diff --git a/.cargo/config.toml b/.cargo/config.toml
index 6a9915927..847ca2408 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -1,7 +1,4 @@
[env]
-# libkrun embeds a Linux guest init. Native Linux builds use cc; macOS builds
-# cross-compile that init with Zig instead of accidentally using Apple clang.
-CC_LINUX = { value = "scripts/libkrun-linux-cc", relative = true }
# Debug DataFusion/Lance projection uses more than the default 2MiB test-thread
# stack, which overflowed canonical event import on Linux CI.
RUST_MIN_STACK = "8388608"
diff --git a/.github/actions/setup-build-env/action.yml b/.github/actions/setup-build-env/action.yml
index e5f7210dc..c34b987e2 100644
--- a/.github/actions/setup-build-env/action.yml
+++ b/.github/actions/setup-build-env/action.yml
@@ -24,7 +24,7 @@ inputs:
install-zig:
description: "Install Zig for the macOS-to-Linux guest init cross-build"
required: false
- default: "true"
+ default: "false"
runs:
using: "composite"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8e937b4f5..d85a344cf 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -127,15 +127,8 @@ jobs:
packages: >-
persisting-agentctl
persisting-events
- persisting-overlay-core
- persisting-overlayfs
persisting-overlaynet
- persisting-replay
event_control: true
- prepare_pvisor: false
- prepare_runtime: false
- smoke_pvisor: false
- smoke_ppilot: false
smoke_pchronicle: false
install_zig: false
- platform: Linux
@@ -143,32 +136,6 @@ jobs:
shard: gateway
packages: persisting-gateway
event_control: false
- prepare_pvisor: false
- prepare_runtime: false
- smoke_pvisor: false
- smoke_ppilot: false
- smoke_pchronicle: false
- install_zig: false
- - platform: Linux
- os: ubuntu-latest
- shard: pvisor
- packages: persisting-pvisor
- event_control: false
- prepare_pvisor: true
- prepare_runtime: false
- smoke_pvisor: true
- smoke_ppilot: false
- smoke_pchronicle: false
- install_zig: false
- - platform: Linux
- os: ubuntu-latest
- shard: ppilot
- packages: persisting-ppilot
- event_control: false
- prepare_pvisor: false
- prepare_runtime: true
- smoke_pvisor: false
- smoke_ppilot: true
smoke_pchronicle: false
install_zig: false
- platform: Linux
@@ -178,43 +145,19 @@ jobs:
persisting-pchronicle
persisting-pchronicle-cli
event_control: false
- prepare_pvisor: false
- prepare_runtime: false
- smoke_pvisor: false
- smoke_ppilot: false
smoke_pchronicle: true
install_zig: false
- platform: macOS
os: macos-latest
- shard: agent-runtime
+ shard: capture
packages: >-
persisting-agentctl
persisting-events
persisting-gateway
- persisting-overlay-core
- persisting-overlayfs
persisting-overlaynet
- persisting-ppilot
- persisting-pvisor
- persisting-replay
- event_control: false
- prepare_pvisor: false
- prepare_runtime: true
- smoke_pvisor: true
- smoke_ppilot: true
- smoke_pchronicle: false
- install_zig: true
- - platform: macOS
- os: macos-latest
- shard: pvisor
- packages: persisting-pvisor
event_control: false
- prepare_pvisor: true
- prepare_runtime: false
- smoke_pvisor: true
- smoke_ppilot: false
smoke_pchronicle: false
- install_zig: true
+ install_zig: false
- platform: macOS
os: macos-latest
shard: pchronicle
@@ -222,10 +165,6 @@ jobs:
persisting-pchronicle
persisting-pchronicle-cli
event_control: false
- prepare_pvisor: false
- prepare_runtime: false
- smoke_pvisor: false
- smoke_ppilot: false
smoke_pchronicle: true
install_zig: false
steps:
@@ -241,39 +180,13 @@ jobs:
with:
shared-key: ci-rust-${{ matrix.shard }}
- # macOS needs the Hypervisor entitlement on the product binary. Running
- # the same recipe on Linux also keeps the smoke-test setup identical.
- - name: Build pVisor product binary
- if: matrix.prepare_pvisor
- run: just pvisor debug
-
- # pPilot's integration tests resolve the real pVisor and pChronicle
- # executables from PATH. Build them once in this shard before nextest.
- - name: Build Agent runtime component set
- if: matrix.prepare_runtime
- run: |
- just build-agent-runtime debug
- echo "${GITHUB_WORKSPACE}/target/debug" >> "${GITHUB_PATH}"
-
- name: Run ${{ matrix.shard }} tests
- env:
- # GitHub-hosted Linux runners may disable unprivileged user
- # namespaces. The dedicated isolation job remains strict.
- PERSISTING_TEST_ALLOW_NO_USERNS: "1"
run: just ci-nextest ${{ matrix.packages }}
- name: Test shared event control contract
if: matrix.event_control
run: just test-events-control
- - name: Smoke pVisor CLI
- if: matrix.smoke_pvisor
- run: just smoke-pvisor-cli
-
- - name: Smoke pPilot CLI
- if: matrix.smoke_ppilot
- run: just smoke-ppilot-cli
-
- name: Smoke pChronicle CLI
if: matrix.smoke_pchronicle
run: just smoke-pchronicle-cli
@@ -321,47 +234,6 @@ jobs:
- name: Build and test Web crate
run: just test-pchronicle-web
- pvisor-isolation-regression:
- name: pVisor Isolation Regression / Linux
- runs-on: ubuntu-latest
- timeout-minutes: 30
- steps:
- - uses: actions/checkout@v4
-
- - name: Setup Build Environment
- uses: ./.github/actions/setup-build-env
- with:
- install-zig: "false"
- components: ""
-
- - uses: Swatinem/rust-cache@v2
- with:
- shared-key: ci-pvisor-isolation
-
- - name: Enable rootless isolation primitives
- shell: bash
- run: |
- sudo apt-get update -qq
- sudo apt-get install -y -qq --no-install-recommends fuse3 jq util-linux
- if sysctl kernel.unprivileged_userns_clone >/dev/null 2>&1; then
- sudo sysctl -w kernel.unprivileged_userns_clone=1
- fi
- if sysctl kernel.apparmor_restrict_unprivileged_userns >/dev/null 2>&1; then
- sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
- fi
- test -c /dev/fuse
- unshare --user --map-root-user --mount --net /bin/true
-
- - name: Run strict pVisor isolation regressions
- env:
- PERSISTING_RUN_HOME: ${{ runner.temp }}/pvisor-isolation-runs
- run: just test-pvisor-isolation
-
- - name: Run pVisor filesystem examples
- env:
- WORK_ROOT: ${{ runner.temp }}/pvisor-filesystem-examples
- run: just examples-pvisor-filesystem
-
s3-contract:
name: pChronicle S3 Contract / MinIO
runs-on: ubuntu-latest
@@ -379,28 +251,41 @@ jobs:
with:
shared-key: ci-pchronicle-s3
+ - uses: actions/setup-go@v5
+ with:
+ go-version: "1.24.2"
+ cache: false
+
+ # Upstream no longer distributes the public container images or binaries.
+ - name: Build pinned MinIO tools from source
+ env:
+ GOBIN: ${{ runner.temp }}/minio-bin
+ run: |
+ go install github.com/minio/minio@RELEASE.2025-07-23T15-54-02Z
+ go install github.com/minio/mc@RELEASE.2025-08-13T08-35-41Z
+ echo "$GOBIN" >> "$GITHUB_PATH"
+
- name: Start pinned MinIO server
+ env:
+ MINIO_ROOT_USER: minioadmin
+ MINIO_ROOT_PASSWORD: minioadmin123
run: |
- docker run --detach --name persisting-minio-contract \
- --publish 9000:9000 \
- --env MINIO_ROOT_USER=minioadmin \
- --env MINIO_ROOT_PASSWORD=minioadmin123 \
- quay.io/minio/minio:RELEASE.2025-07-23T15-54-02Z \
- server /data
+ nohup minio server "$RUNNER_TEMP/minio-data" --address 127.0.0.1:9000 \
+ > "$RUNNER_TEMP/minio.log" 2>&1 &
+ echo $! > "$RUNNER_TEMP/minio.pid"
for attempt in $(seq 1 30); do
if curl --fail --silent http://127.0.0.1:9000/minio/health/live >/dev/null; then
exit 0
fi
sleep 1
done
- docker logs persisting-minio-contract
+ cat "$RUNNER_TEMP/minio.log"
exit 1
- name: Create isolated test bucket
run: |
- docker run --rm --network host --entrypoint /bin/sh \
- quay.io/minio/mc:RELEASE.2025-08-13T08-35-41Z -c \
- 'mc alias set local http://127.0.0.1:9000 minioadmin minioadmin123 && mc mb --ignore-existing local/persisting-tests'
+ mc alias set local http://127.0.0.1:9000 minioadmin minioadmin123
+ mc mb --ignore-existing local/persisting-tests
- name: Run real S3 storage contract
env:
@@ -416,11 +301,14 @@ jobs:
- name: Show MinIO logs after failure
if: failure()
- run: docker logs persisting-minio-contract
+ run: cat "$RUNNER_TEMP/minio.log"
- name: Stop MinIO
if: always()
- run: docker rm --force persisting-minio-contract || true
+ run: |
+ if [ -f "$RUNNER_TEMP/minio.pid" ]; then
+ kill "$(cat "$RUNNER_TEMP/minio.pid")" || true
+ fi
python-test:
name: Python Test
@@ -441,44 +329,6 @@ jobs:
- name: Pytest
run: just test-py
- pvisor-cases:
- name: pVisor case checklist
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - name: Setup Build Environment
- uses: ./.github/actions/setup-build-env
- with:
- install-nextest: "false"
- install-zig: "false"
- components: ""
-
- - uses: Swatinem/rust-cache@v2
- with:
- shared-key: ci-pvisor-cases
-
- - name: Run documented cases
- run: |
- just cases pvisor \
- --case A01,A02,A03,A04,A05,A06 \
- --case B01,B02,B03,B04 \
- --case C02,C03,C04 \
- --case D01,D03 \
- --case F01,F02,F03 \
- --case G01,G02,G03,G06 \
- --case H01,H02 \
- --case I01,I02,I03 \
- --case J03
-
- - name: Upload case report
- if: always()
- uses: actions/upload-artifact@v4
- with:
- name: pvisor-case-report
- path: target/pvisor-case-report.md
- if-no-files-found: ignore
-
examples:
name: Examples / ${{ matrix.shard }}
runs-on: ubuntu-latest
@@ -486,12 +336,6 @@ jobs:
fail-fast: false
matrix:
include:
- - shard: pvisor
- command: |
- just test-pvisor-benchmark
- just examples-pvisor-portable
- - shard: ppilot
- command: just examples-ppilot
- shard: pchronicle
command: just examples-pchronicle
steps:
@@ -535,10 +379,8 @@ jobs:
- rust-test
- rust-proptest
- pchronicle-web-test
- - pvisor-isolation-regression
- s3-contract
- python-test
- - pvisor-cases
- examples
runs-on: ubuntu-latest
steps:
diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml
index 86d9903fe..5c64e3c75 100644
--- a/.github/workflows/nightly.yml
+++ b/.github/workflows/nightly.yml
@@ -129,7 +129,7 @@ jobs:
curl -fsSL https://raw.githubusercontent.com/${{ github.repository }}/main/scripts/install-nightly.sh | bash
```
- The wheel includes the matched `pchronicle`, `pvisor`, and `ppilot` CLI component set. Built with local version `+${{ needs.meta.outputs.local_version }}` (base version from `pyproject.toml`).
+ The wheel includes the `pchronicle` CLI. Built with local version `+${{ needs.meta.outputs.local_version }}` (base version from `pyproject.toml`).
publish-benchmark-readme:
name: Publish benchmark summary to README
diff --git a/.github/workflows/pchronicle-benchmark.yml b/.github/workflows/pchronicle-benchmark.yml
index 6783ab4cb..0ff9c3d60 100644
--- a/.github/workflows/pchronicle-benchmark.yml
+++ b/.github/workflows/pchronicle-benchmark.yml
@@ -16,14 +16,10 @@ permissions:
jobs:
benchmark:
- name: Benchmark (${{ matrix.ref }})
+ name: Benchmark (main and candidate)
runs-on: ubuntu-latest
- timeout-minutes: 45
- strategy:
- fail-fast: false
- max-parallel: 2
- matrix:
- ref: [main, candidate]
+ timeout-minutes: 60
+ # Both revisions must run on the same machine for comparable CPU timings.
steps:
- name: Checkout candidate
uses: actions/checkout@v4
@@ -43,10 +39,9 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
- shared-key: pchronicle-benchmark-${{ matrix.ref }}
+ shared-key: pchronicle-benchmark
- name: Resolve main baseline
- if: matrix.ref == 'main'
id: baseline
shell: bash
run: |
@@ -71,7 +66,7 @@ jobs:
fi
- name: Benchmark main
- if: matrix.ref == 'main' && steps.baseline.outputs.supported == 'true'
+ if: steps.baseline.outputs.supported == 'true'
shell: bash
run: |
set -euo pipefail
@@ -84,7 +79,6 @@ jobs:
--output "${RUNNER_TEMP}/pchronicle-benchmark/main"
- name: Benchmark candidate
- if: matrix.ref == 'candidate'
shell: bash
run: |
set -euo pipefail
@@ -99,7 +93,7 @@ jobs:
if: always()
uses: actions/upload-artifact@v4
with:
- name: pchronicle-benchmark-part-${{ matrix.ref }}-${{ github.run_id }}
+ name: pchronicle-benchmark-part-${{ github.run_id }}
path: ${{ runner.temp }}/pchronicle-benchmark
retention-days: 30
if-no-files-found: warn
diff --git a/.github/workflows/pvisor-benchmark.yml b/.github/workflows/pvisor-benchmark.yml
deleted file mode 100644
index 462352333..000000000
--- a/.github/workflows/pvisor-benchmark.yml
+++ /dev/null
@@ -1,101 +0,0 @@
-name: pVisor Benchmark
-
-on:
- push:
- branches: [main, master]
- pull_request:
- branches: [main, master]
- workflow_dispatch:
-
-concurrency:
- group: pvisor-benchmark-${{ github.ref }}
- cancel-in-progress: true
-
-permissions:
- contents: read
-
-jobs:
- compare:
- name: Process and Run Bundle report
- runs-on: ubuntu-latest
- timeout-minutes: 30
- steps:
- - name: Checkout candidate
- uses: actions/checkout@v4
- with:
- fetch-depth: 0
-
- - name: Setup Build Environment
- uses: ./.github/actions/setup-build-env
- with:
- components: ""
-
- - uses: Swatinem/rust-cache@v2
- with:
- shared-key: pvisor-benchmark
-
- - name: Resolve main baseline
- id: baseline
- shell: bash
- run: |
- set -euo pipefail
- if [[ "${{ github.event_name }}" == "pull_request" ]]; then
- baseline_ref="origin/${{ github.base_ref }}"
- elif git rev-parse --verify HEAD^ >/dev/null 2>&1; then
- baseline_ref="HEAD^"
- else
- baseline_ref="HEAD"
- fi
- baseline_sha="$(git rev-parse "$baseline_ref")"
- baseline_repo="${RUNNER_TEMP}/persisting-main"
- git worktree add --detach "$baseline_repo" "$baseline_sha"
- echo "repo=$baseline_repo" >> "$GITHUB_OUTPUT"
- echo "sha=$baseline_sha" >> "$GITHUB_OUTPUT"
- if [[ -x "$baseline_repo/benchmark/pvisor/run.sh" ]] && \
- grep -q '^benchmark-pvisor ' "$baseline_repo/justfile"; then
- echo "supported=true" >> "$GITHUB_OUTPUT"
- else
- echo "supported=false" >> "$GITHUB_OUTPUT"
- fi
-
- - name: Benchmark main
- if: steps.baseline.outputs.supported == 'true'
- shell: bash
- run: |
- set -euo pipefail
- cd "${{ steps.baseline.outputs.repo }}"
- just benchmark-pvisor \
- smoke \
- "${RUNNER_TEMP}/pvisor-benchmark/main" \
- "${GITHUB_WORKSPACE}/target/pvisor-benchmark-main"
-
- - name: Benchmark candidate
- shell: bash
- run: |
- just benchmark-pvisor \
- smoke \
- "${RUNNER_TEMP}/pvisor-benchmark/candidate" \
- "${GITHUB_WORKSPACE}/target/pvisor-benchmark-candidate"
-
- - name: Compare reports
- shell: bash
- run: |
- set -euo pipefail
- candidate="${RUNNER_TEMP}/pvisor-benchmark/candidate/raw-report.json"
- baseline="${RUNNER_TEMP}/pvisor-benchmark/main/raw-report.json"
- output="${RUNNER_TEMP}/pvisor-benchmark/comparison"
- if [[ -f "$baseline" ]]; then
- just benchmark-pvisor-compare "$candidate" "$baseline" "$output"
- else
- just benchmark-pvisor-compare "$candidate" "" "$output"
- fi
- cat "$output/report.md" >> "$GITHUB_STEP_SUMMARY"
-
- - name: Upload benchmark data and report
- if: always()
- uses: actions/upload-artifact@v4
- with:
- name: pvisor-benchmark-${{ github.run_id }}
- path: ${{ runner.temp }}/pvisor-benchmark
- retention-days: 30
- if-no-files-found: error
diff --git a/.gitignore b/.gitignore
index dc9f42270..5ee29b208 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,12 +14,6 @@ crates/persisting-engine/ds/
# Local review, benchmark, and runtime artifacts
/data/
/tmp/
-/examples/pvisor/lease.lock
-/examples/pvisor/run.json
-/examples/pvisor/run-bundle.json
-/examples/pvisor/03-network-isolation/lease.lock
-/examples/pvisor/03-network-isolation/run.json
-/examples/pvisor/03-network-isolation/run-bundle.json
# persisting-dlcapt runtime / release artifacts
crates/persisting-dlcapt/var/
diff --git a/AGENTS.md b/AGENTS.md
index ba17f3822..bf714448b 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -25,9 +25,9 @@ This exclusion covers, in particular:
- `crates/persisting-dlcapt/` and dlcapt-specific scripts, tests, features, and
workflows
-The default active scope is the Agent infrastructure centered on pVisor,
-pPilot, pChronicle, Gateway, Control, OverlayFS, OverlayNet, and trajectory CLI
-surfaces. `persisting-dlcapt` is a separate standalone component; excluding it
+The default active scope is pChronicle, its trajectory CLI and Web UI, and
+the Gateway, Control, events, and OverlayNet libraries it depends on.
+`persisting-dlcapt` is a separate standalone component; excluding it
does not exclude Gateway trajectory capture or pChronicle capture storage.
Enter an excluded subsystem only when:
diff --git a/Cargo.lock b/Cargo.lock
index 2a5f13ba3..1a15eb697 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -79,22 +79,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
- "anstyle-parse 0.2.7",
- "anstyle-query",
- "anstyle-wincon",
- "colorchoice",
- "is_terminal_polyfill",
- "utf8parse",
-]
-
-[[package]]
-name = "anstream"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
-dependencies = [
- "anstyle",
- "anstyle-parse 1.0.0",
+ "anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
@@ -117,15 +102,6 @@ dependencies = [
"utf8parse",
]
-[[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"
@@ -991,25 +967,6 @@ dependencies = [
"serde",
]
-[[package]]
-name = "bincode"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740"
-dependencies = [
- "bincode_derive",
- "unty",
-]
-
-[[package]]
-name = "bincode_derive"
-version = "2.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09"
-dependencies = [
- "virtue",
-]
-
[[package]]
name = "bit-set"
version = "0.8.0"
@@ -1173,34 +1130,6 @@ dependencies = [
"either",
]
-[[package]]
-name = "bzip2"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47"
-dependencies = [
- "bzip2-sys",
-]
-
-[[package]]
-name = "bzip2-sys"
-version = "0.1.13+1.0.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14"
-dependencies = [
- "cc",
- "pkg-config",
-]
-
-[[package]]
-name = "caps"
-version = "0.5.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd1ddba47aba30b6a889298ad0109c3b8dcb0e8fc993b459daa7067d46f865e0"
-dependencies = [
- "libc",
-]
-
[[package]]
name = "cast"
version = "0.3.0"
@@ -1343,7 +1272,7 @@ version = "4.5.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
dependencies = [
- "anstream 0.6.21",
+ "anstream",
"anstyle",
"clap_lex",
"strsim",
@@ -1367,15 +1296,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
-[[package]]
-name = "clru"
-version = "0.6.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "197fd99cb113a8d5d9b6376f3aa817f32c1078f2343b714fff7d2ca44fdf67d5"
-dependencies = [
- "hashbrown 0.16.1",
-]
-
[[package]]
name = "cmake"
version = "0.1.58"
@@ -2587,29 +2507,6 @@ dependencies = [
"cfg-if 1.0.4",
]
-[[package]]
-name = "env_filter"
-version = "2.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217"
-dependencies = [
- "log",
- "regex",
-]
-
-[[package]]
-name = "env_logger"
-version = "0.11.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6"
-dependencies = [
- "anstream 1.0.0",
- "anstyle",
- "env_filter",
- "jiff",
- "log",
-]
-
[[package]]
name = "equivalent"
version = "1.0.2"
@@ -2626,16 +2523,6 @@ dependencies = [
"windows-sys 0.61.2",
]
-[[package]]
-name = "etcetera"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96"
-dependencies = [
- "cfg-if 1.0.4",
- "windows-sys 0.61.2",
-]
-
[[package]]
name = "ethnum"
version = "1.5.3"
@@ -2669,32 +2556,12 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8eb564c5c7423d25c886fb561d1e4ee69f72354d16918afa32c08811f6b6a55"
-[[package]]
-name = "faster-hex"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73"
-dependencies = [
- "heapless 0.8.0",
- "serde",
-]
-
[[package]]
name = "fastrand"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
-[[package]]
-name = "filetime"
-version = "0.2.29"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759"
-dependencies = [
- "cfg-if 1.0.4",
- "libc",
-]
-
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
@@ -2807,21 +2674,6 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
-[[package]]
-name = "fuser"
-version = "0.15.1"
-dependencies = [
- "libc",
- "libloading",
- "log",
- "memchr",
- "nix 0.29.0",
- "page_size",
- "pkg-config",
- "smallvec",
- "zerocopy",
-]
-
[[package]]
name = "futures"
version = "0.3.32"
@@ -3131,906 +2983,172 @@ dependencies = [
]
[[package]]
-name = "gix"
-version = "0.85.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa8b2e38ebfc4484dfef8580ddcaf8abb7285e6f3eb6413ff6775d104ae96ca6"
-dependencies = [
- "gix-actor",
- "gix-attributes",
- "gix-command",
- "gix-commitgraph",
- "gix-config",
- "gix-date",
- "gix-diff",
- "gix-discover",
- "gix-error",
- "gix-features",
- "gix-filter",
- "gix-fs",
- "gix-glob",
- "gix-hash",
- "gix-hashtable",
- "gix-ignore",
- "gix-index",
- "gix-lock",
- "gix-object",
- "gix-odb",
- "gix-pack",
- "gix-path",
- "gix-pathspec",
- "gix-protocol",
- "gix-ref",
- "gix-refspec",
- "gix-revision",
- "gix-revwalk",
- "gix-sec",
- "gix-shallow",
- "gix-submodule",
- "gix-tempfile",
- "gix-trace",
- "gix-traverse",
- "gix-url",
- "gix-utils",
- "gix-validate",
- "gix-worktree",
- "gix-worktree-stream",
- "nonempty",
- "smallvec",
- "thiserror 2.0.18",
-]
+name = "glob"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
-name = "gix-actor"
-version = "0.41.2"
+name = "gloo-timers"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33f9308ad6fd35b2a865cbe4117ac61b2be59e4a9ef1621c7a9794f7c8e52c5b"
+checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"
dependencies = [
- "bstr",
- "gix-date",
- "gix-error",
+ "futures-channel",
+ "futures-core",
+ "js-sys",
+ "wasm-bindgen",
]
[[package]]
-name = "gix-attributes"
-version = "0.33.2"
+name = "goosefs-sdk"
+version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39b40888d0ed415c0744a6cdc61eebf0304c9d26ab726725b718443c322e5ba4"
+checksum = "e1ea4eee6dcbc31b25ab4fd577adc55b677d2bed3aa3016c44c58fbe1b2298a5"
dependencies = [
- "bstr",
- "gix-glob",
- "gix-path",
- "gix-quote",
- "gix-trace",
- "kstring",
- "smallvec",
+ "arc-swap",
+ "async-trait",
+ "bytes",
+ "dashmap",
+ "fastrand",
+ "futures",
+ "hostname",
+ "io-uring",
+ "itoa",
+ "libc",
+ "lru 0.18.4",
+ "memmap2",
+ "moka",
+ "prost",
+ "prost-types",
+ "rand 0.9.4",
+ "reqwest 0.12.28",
+ "serde",
"thiserror 2.0.18",
- "unicode-bom",
+ "tokio",
+ "tokio-stream",
+ "tonic",
+ "tonic-prost",
+ "tracing",
+ "uuid",
+ "xxhash-rust",
]
[[package]]
-name = "gix-bitmap"
-version = "0.3.3"
+name = "h2"
+version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cd1d118d0f5d88b96e6f6e13b566475fef4797ead4a02c26fed36c1375066f7"
+checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
dependencies = [
- "gix-error",
+ "atomic-waker",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "http 1.4.0",
+ "indexmap 2.14.0",
+ "slab",
+ "tokio",
+ "tokio-util",
+ "tracing",
]
[[package]]
-name = "gix-chunk"
-version = "0.7.3"
+name = "half"
+version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2a871e5cab12ba568845714473505deefffb3c04eb47f4708ce344cd459c1cc"
+checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
dependencies = [
- "gix-error",
+ "bytemuck",
+ "cfg-if 1.0.4",
+ "crunchy",
+ "num-traits",
+ "zerocopy",
]
[[package]]
-name = "gix-command"
-version = "0.9.1"
+name = "hash32"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00706d4fef135ef4b01680d5218c6ee40cda8baf697b864296cbc887d19118f6"
+checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
dependencies = [
- "bstr",
- "gix-path",
- "gix-quote",
- "gix-trace",
- "shell-words",
+ "byteorder",
]
[[package]]
-name = "gix-commitgraph"
-version = "0.37.1"
+name = "hashbrown"
+version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f675d0df484a7f6a47e64bd6f311af489d947c0323b0564f36d14f3d7762abb"
-dependencies = [
- "bstr",
- "gix-chunk",
- "gix-error",
- "gix-hash",
- "memmap2",
- "nonempty",
-]
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
-name = "gix-config"
-version = "0.58.0"
+name = "hashbrown"
+version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a29bf266c4cdaf759e535c24ad4ce655b987aeb6911075643403cc7cc5ade583"
-dependencies = [
- "bstr",
- "gix-config-value",
- "gix-features",
- "gix-glob",
- "gix-path",
- "gix-ref",
- "gix-sec",
- "smallvec",
- "thiserror 2.0.18",
- "unicode-bom",
-]
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
-name = "gix-config-value"
-version = "0.18.1"
+name = "hashbrown"
+version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed42168329552f6c2e5df09665c104199d45d84bedb53683738a49b57fe1baab"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
- "bitflags 2.11.1",
- "bstr",
- "gix-path",
- "libc",
- "thiserror 2.0.18",
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.1.5",
]
[[package]]
-name = "gix-date"
-version = "0.15.6"
+name = "hashbrown"
+version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e47b9e8cdc688296609b706428de570f88b1e0eed7156dde7b4a89d26fa4567"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
- "bstr",
- "gix-error",
- "itoa",
- "jiff",
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
]
[[package]]
-name = "gix-diff"
-version = "0.65.0"
+name = "hashbrown"
+version = "0.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92c6d56c94edf92d78203a1cd416f770e35e10b6955ede6b9d7d0c22ff88a5f3"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
dependencies = [
- "bstr",
- "gix-command",
- "gix-filter",
- "gix-fs",
- "gix-hash",
- "gix-imara-diff",
- "gix-object",
- "gix-path",
- "gix-tempfile",
- "gix-trace",
- "gix-traverse",
- "gix-worktree",
- "thiserror 2.0.18",
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
]
[[package]]
-name = "gix-discover"
-version = "0.53.0"
+name = "heapify"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0049b265b7f201ca9ab25475b22b47fe444060126a51abe00f77d986fc5cc52e"
+
+[[package]]
+name = "heapless"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d624d5b23b10c1d85337645227abe353ac95ab8ff66a7bdd5ce689b2db33a722"
+checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
dependencies = [
- "bstr",
- "dunce",
- "gix-fs",
- "gix-path",
- "gix-ref",
- "gix-sec",
- "thiserror 2.0.18",
+ "hash32",
+ "stable_deref_trait",
]
[[package]]
-name = "gix-error"
-version = "0.2.5"
+name = "heapless"
+version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a9292309fd944e71b2a3c96d3c03a6feb8852db646febdde7cbb9f79cb5f329"
+checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4"
dependencies = [
- "bstr",
+ "hash32",
+ "stable_deref_trait",
]
[[package]]
-name = "gix-features"
-version = "0.48.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1849ae154d38bc403185be14fa871e38e3c93ee606875d94e207fdb9fba52dbc"
-dependencies = [
- "bytes",
- "crc32fast",
- "crossbeam-channel",
- "gix-path",
- "gix-trace",
- "gix-utils",
- "libc",
- "once_cell",
- "parking_lot",
- "prodash",
- "thiserror 2.0.18",
- "walkdir",
- "zlib-rs",
-]
-
-[[package]]
-name = "gix-filter"
-version = "0.32.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6644fb2ef97928c278675b239f366b457103d7e436f811d27331a8daf212759c"
-dependencies = [
- "bstr",
- "encoding_rs",
- "gix-attributes",
- "gix-command",
- "gix-hash",
- "gix-object",
- "gix-packetline",
- "gix-path",
- "gix-quote",
- "gix-trace",
- "gix-utils",
- "smallvec",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-fs"
-version = "0.21.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6cdff46db8798e47e2f727d84b9379aac5add3dd3d9d0b07bb4d7d5d640771fe"
-dependencies = [
- "bstr",
- "fastrand",
- "gix-features",
- "gix-path",
- "gix-utils",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-glob"
-version = "0.26.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1fcb8ef5b16bcf874abe9b68d8abb3c0493c876d367ab824151f30a0f3f3756"
-dependencies = [
- "bitflags 2.11.1",
- "bstr",
- "gix-features",
- "gix-path",
-]
-
-[[package]]
-name = "gix-hash"
-version = "0.25.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb0926d3819c837750b4e03c7754901e73f68b8c9b690753a6372a1bed4eedce"
-dependencies = [
- "faster-hex",
- "gix-features",
- "sha1-checked",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-hashtable"
-version = "0.15.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e261d54091f0d1c729bc83f54548c071bdec60a697de1e58e88bdfd7a99d24e"
-dependencies = [
- "gix-hash",
- "hashbrown 0.17.1",
- "parking_lot",
-]
-
-[[package]]
-name = "gix-ignore"
-version = "0.21.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d491bab9bf2c9f341dc754f425c31d5d3f63aca615312167b82e1deeaca97d8d"
-dependencies = [
- "bstr",
- "gix-glob",
- "gix-path",
- "gix-trace",
- "unicode-bom",
-]
-
-[[package]]
-name = "gix-imara-diff"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a791e6620676a875f362f3156ed213e73ca099a09bf992c18812abe65cc37b1"
-dependencies = [
- "bstr",
- "hashbrown 0.17.1",
-]
-
-[[package]]
-name = "gix-index"
-version = "0.53.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36d45f82ec5a4d7542ea595e9ad16e03e26c8cb4f221e5bc9fcdcf469f63a681"
-dependencies = [
- "bitflags 2.11.1",
- "bstr",
- "filetime",
- "fnv",
- "gix-bitmap",
- "gix-features",
- "gix-fs",
- "gix-hash",
- "gix-lock",
- "gix-object",
- "gix-traverse",
- "gix-utils",
- "gix-validate",
- "hashbrown 0.17.1",
- "itoa",
- "libc",
- "memmap2",
- "rustix",
- "smallvec",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-lock"
-version = "23.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65c9dedd9e90b0d47624d2ed241d394e09294118364e87b9b7e5f1fe755f3c2c"
-dependencies = [
- "gix-tempfile",
- "gix-utils",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-object"
-version = "0.62.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "019b38afc3eac1e41f9fe09a327664b313ba4a120fa5f40e3678795d0e42783e"
-dependencies = [
- "bstr",
- "gix-actor",
- "gix-date",
- "gix-features",
- "gix-hash",
- "gix-hashtable",
- "gix-utils",
- "gix-validate",
- "itoa",
- "smallvec",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-odb"
-version = "0.82.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fadc59f6fa0f9dd445eceee61060a2b59ca557f48da9fc677f567db535b782a"
-dependencies = [
- "arc-swap",
- "gix-features",
- "gix-fs",
- "gix-hash",
- "gix-hashtable",
- "gix-object",
- "gix-pack",
- "gix-path",
- "gix-quote",
- "memmap2",
- "parking_lot",
- "tempfile",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-pack"
-version = "0.72.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ca3e7f1726cd2c0cd1cf1fc20be8a8e623f0b163f1f8d6fc836cfb9bc8cd758b"
-dependencies = [
- "clru",
- "gix-chunk",
- "gix-error",
- "gix-features",
- "gix-hash",
- "gix-hashtable",
- "gix-object",
- "gix-path",
- "memmap2",
- "smallvec",
- "thiserror 2.0.18",
- "uluru",
-]
-
-[[package]]
-name = "gix-packetline"
-version = "0.21.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b217dd0ee0c4021ecf169a4a519b1b4f80d15e3f3765f3dc466223dc0ac891d7"
-dependencies = [
- "bstr",
- "faster-hex",
- "gix-trace",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-path"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ed3e8d7a82e886e17a72e03d4ba0c13db6f2219b6cd4e2900b4cae426ec20c9"
-dependencies = [
- "bstr",
- "gix-trace",
- "gix-validate",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-pathspec"
-version = "0.18.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3050783b41ee11511e1e8fb35623df81806194f4030395f14f48ea37c2798c9f"
-dependencies = [
- "bitflags 2.11.1",
- "bstr",
- "gix-attributes",
- "gix-config-value",
- "gix-glob",
- "gix-path",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-protocol"
-version = "0.63.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "978468bae4ea2df20c72db3b20d0bdb548a0c1090b85a83643b553e6e0e041f2"
-dependencies = [
- "bstr",
- "gix-date",
- "gix-features",
- "gix-hash",
- "gix-ref",
- "gix-shallow",
- "gix-transport",
- "gix-utils",
- "maybe-async",
- "nonempty",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-quote"
-version = "0.7.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6e541fc33cc2b783b7979040d445a0c86a2eca747c8faea4ca84230d06ae6ef"
-dependencies = [
- "bstr",
- "gix-error",
- "gix-utils",
-]
-
-[[package]]
-name = "gix-ref"
-version = "0.65.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9bbfbce1dfd7d7f8469ddef6d3518376aff664348f153cbe0fc3e58ef993d24e"
-dependencies = [
- "gix-actor",
- "gix-features",
- "gix-fs",
- "gix-hash",
- "gix-lock",
- "gix-object",
- "gix-path",
- "gix-tempfile",
- "gix-utils",
- "gix-validate",
- "memmap2",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-refspec"
-version = "0.43.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7bc36a4fb1a1540b59cf2da498783080743fa274b02a3f19ca444fc4015a9d4f"
-dependencies = [
- "bstr",
- "gix-error",
- "gix-glob",
- "gix-hash",
- "gix-revision",
- "gix-validate",
- "smallvec",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-revision"
-version = "0.47.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "885075c3c21eb9c06e0be3b3728ba5932c04e1c1011dcee7c81801980e3e986f"
-dependencies = [
- "bstr",
- "gix-commitgraph",
- "gix-date",
- "gix-error",
- "gix-hash",
- "gix-object",
- "gix-revwalk",
- "nonempty",
-]
-
-[[package]]
-name = "gix-revwalk"
-version = "0.33.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f11fe7ca2585193d3d70bbe0be175a2008d883a704cc7a55e454e113e689455"
-dependencies = [
- "gix-commitgraph",
- "gix-date",
- "gix-error",
- "gix-hash",
- "gix-hashtable",
- "gix-object",
- "smallvec",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-sec"
-version = "0.14.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af4fe6c152c1d50aea36f299825702cd37e303307832fec1d0fdd5844e47ce2f"
-dependencies = [
- "bitflags 2.11.1",
- "gix-path",
- "libc",
- "windows-sys 0.61.2",
-]
-
-[[package]]
-name = "gix-shallow"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a292fc2fe548c5dfa575479d16b445b0ddf1dd2f56f1fec6aed386f82553cd97"
-dependencies = [
- "bstr",
- "gix-hash",
- "gix-lock",
- "nonempty",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-submodule"
-version = "0.32.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a7f9f594f7cbda0b38ba6b633b3e9a7b7901acdc5d27bc186a16633800cd1ac8"
-dependencies = [
- "bstr",
- "gix-config",
- "gix-path",
- "gix-pathspec",
- "gix-refspec",
- "gix-url",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-tempfile"
-version = "23.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27850097e1ff9515f46a0dad0f5f9c9d020e972727772dabab9450690c4adb22"
-dependencies = [
- "dashmap",
- "gix-fs",
- "libc",
- "parking_lot",
- "tempfile",
-]
-
-[[package]]
-name = "gix-trace"
-version = "0.1.21"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be3eb81d9dc914335923e50d52829c551feefd6a72d176c4130c546b67a60814"
-
-[[package]]
-name = "gix-transport"
-version = "0.57.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "186874f7ad1fb2f9a2f2aa9c2dabc7f9dd087bef74c1a0eee2b4a9cf0248fcb3"
-dependencies = [
- "bstr",
- "gix-command",
- "gix-features",
- "gix-packetline",
- "gix-quote",
- "gix-sec",
- "gix-url",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-traverse"
-version = "0.59.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5062cca8f2977565bbaf666ec31dbdb9bc9d9293beb65f9bec52e6c1121b62a1"
-dependencies = [
- "bitflags 2.11.1",
- "gix-commitgraph",
- "gix-date",
- "gix-hash",
- "gix-hashtable",
- "gix-object",
- "gix-revwalk",
- "smallvec",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-url"
-version = "0.36.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57d68e70e96da0e5f9c871f1566349e0fd0e1a20bb483c7f54af1dd0b85b4b29"
-dependencies = [
- "bstr",
- "gix-path",
- "percent-encoding",
- "thiserror 2.0.18",
-]
-
-[[package]]
-name = "gix-utils"
-version = "0.3.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1795bd2a970ca8b2185318c2abb97d955c71992f1cf28de73ad3b593a9f3ce8"
-dependencies = [
- "fastrand",
- "getrandom 0.4.2",
- "unicode-normalization",
-]
-
-[[package]]
-name = "gix-validate"
-version = "0.11.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a034e84d1e04e1b1f20f51f12491da230b6ac8b925d0c8e1b89bcd87a7c5ccc"
-dependencies = [
- "bstr",
-]
-
-[[package]]
-name = "gix-worktree"
-version = "0.54.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92399ed66f259592050c6ed9dc80105e095a2f8e87e6b83d98aa2e21d8e27036"
-dependencies = [
- "bstr",
- "gix-attributes",
- "gix-fs",
- "gix-glob",
- "gix-hash",
- "gix-ignore",
- "gix-index",
- "gix-object",
- "gix-path",
- "gix-validate",
-]
-
-[[package]]
-name = "gix-worktree-stream"
-version = "0.34.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55f3a878c89a05470ad98c644b0015777c530da24854dd29e41fe4f41176840f"
-dependencies = [
- "gix-attributes",
- "gix-error",
- "gix-features",
- "gix-filter",
- "gix-fs",
- "gix-hash",
- "gix-object",
- "gix-path",
- "gix-traverse",
- "parking_lot",
-]
-
-[[package]]
-name = "glob"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
-
-[[package]]
-name = "globset"
-version = "0.4.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e47d37d2ae4464254884b60ab7071be2b876a9c35b696bd018ddcc76847309cd"
-dependencies = [
- "aho-corasick",
- "bstr",
- "log",
- "regex-automata",
- "regex-syntax",
-]
-
-[[package]]
-name = "gloo-timers"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994"
-dependencies = [
- "futures-channel",
- "futures-core",
- "js-sys",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "goosefs-sdk"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e1ea4eee6dcbc31b25ab4fd577adc55b677d2bed3aa3016c44c58fbe1b2298a5"
-dependencies = [
- "arc-swap",
- "async-trait",
- "bytes",
- "dashmap",
- "fastrand",
- "futures",
- "hostname",
- "io-uring",
- "itoa",
- "libc",
- "lru 0.18.4",
- "memmap2",
- "moka",
- "prost",
- "prost-types",
- "rand 0.9.4",
- "reqwest 0.12.28",
- "serde",
- "thiserror 2.0.18",
- "tokio",
- "tokio-stream",
- "tonic",
- "tonic-prost",
- "tracing",
- "uuid",
- "xxhash-rust",
-]
-
-[[package]]
-name = "h2"
-version = "0.4.13"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
-dependencies = [
- "atomic-waker",
- "bytes",
- "fnv",
- "futures-core",
- "futures-sink",
- "http 1.4.0",
- "indexmap 2.14.0",
- "slab",
- "tokio",
- "tokio-util",
- "tracing",
-]
-
-[[package]]
-name = "half"
-version = "2.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
-dependencies = [
- "bytemuck",
- "cfg-if 1.0.4",
- "crunchy",
- "num-traits",
- "zerocopy",
-]
-
-[[package]]
-name = "hash32"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606"
-dependencies = [
- "byteorder",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.12.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
-
-[[package]]
-name = "hashbrown"
-version = "0.14.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
-
-[[package]]
-name = "hashbrown"
-version = "0.15.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
-dependencies = [
- "allocator-api2",
- "equivalent",
- "foldhash 0.1.5",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.16.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
-dependencies = [
- "allocator-api2",
- "equivalent",
- "foldhash 0.2.0",
-]
-
-[[package]]
-name = "hashbrown"
-version = "0.17.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
-dependencies = [
- "allocator-api2",
- "equivalent",
- "foldhash 0.2.0",
-]
-
-[[package]]
-name = "heapify"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0049b265b7f201ca9ab25475b22b47fe444060126a51abe00f77d986fc5cc52e"
-
-[[package]]
-name = "heapless"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad"
-dependencies = [
- "hash32",
- "stable_deref_trait",
-]
-
-[[package]]
-name = "heapless"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25ba4bd83f9415b58b4ed8dc5714c76e626a105be4646c02630ad730ad3b5aa4"
-dependencies = [
- "hash32",
- "stable_deref_trait",
-]
-
-[[package]]
-name = "heck"
-version = "0.5.0"
+name = "heck"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
@@ -4492,27 +3610,6 @@ dependencies = [
"icu_properties",
]
-[[package]]
-name = "imago"
-version = "0.2.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "404f567d70cd6d288e43f95ea8f2d6e8fe6156dd07df7da955cb9b147c4ab2a5"
-dependencies = [
- "async-trait",
- "bincode 2.0.1",
- "cfg-if 1.0.4",
- "futures",
- "libc",
- "maybe-async",
- "miniz_oxide",
- "nix 0.30.1",
- "page_size",
- "rustc_version",
- "tokio",
- "tracing",
- "windows-sys 0.61.2",
-]
-
[[package]]
name = "include_dir"
version = "0.7.4"
@@ -4565,16 +3662,6 @@ dependencies = [
"generic-array",
]
-[[package]]
-name = "interim"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9ce9099a85f468663d3225bf87e85d0548968441e1db12248b996b24f0f5b5a"
-dependencies = [
- "chrono",
- "logos 0.15.1",
-]
-
[[package]]
name = "io-uring"
version = "0.7.12"
@@ -4635,15 +3722,6 @@ dependencies = [
"either",
]
-[[package]]
-name = "itertools"
-version = "0.15.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc"
-dependencies = [
- "either",
-]
-
[[package]]
name = "itoa"
version = "1.0.18"
@@ -4728,64 +3806,6 @@ dependencies = [
"jiff-tzdb",
]
-[[package]]
-name = "jj-lib"
-version = "0.43.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e22e2ced34a1a51f0f449bfff2cbacda1c338b2611268b5684a80251e4c8bf71"
-dependencies = [
- "async-trait",
- "blake2",
- "bstr",
- "chrono",
- "clru",
- "digest 0.10.7",
- "dunce",
- "either",
- "etcetera",
- "futures",
- "gix",
- "gix-ignore",
- "globset",
- "hashbrown 0.17.1",
- "indexmap 2.14.0",
- "interim",
- "itertools 0.15.0",
- "jj-lib-proc-macros",
- "maplit",
- "once_cell",
- "pest",
- "pest_derive",
- "pollster",
- "prost",
- "rand 0.10.1",
- "rand_chacha 0.10.0",
- "rayon",
- "ref-cast",
- "regex",
- "rustix",
- "same-file",
- "serde",
- "smallvec",
- "strsim",
- "tempfile",
- "thiserror 2.0.18",
- "toml_edit 0.25.13+spec-1.1.0",
- "tracing",
- "winreg",
-]
-
-[[package]]
-name = "jj-lib-proc-macros"
-version = "0.43.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c21554e3755e5ecfec934d84ac64d0295d9c2f2b96f6a740a1ddb66addf4142e"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "jni"
version = "0.22.4"
@@ -4894,181 +3914,6 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e037a2e1d8d5fdbd49b16a4ea09d5d6401c1f29eca5ff29d03d3824dba16256a"
-[[package]]
-name = "krun-arch"
-version = "0.1.0-1.19.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e408be4923e881a3fb6536322d569a845e55fb0f5af4a9af3202ed97becbb192"
-dependencies = [
- "krun-arch-gen",
- "krun-smbios",
- "krun-utils",
- "kvm-bindings",
- "kvm-ioctls",
- "libc",
- "vm-memory",
- "vmm-sys-util 0.14.0",
-]
-
-[[package]]
-name = "krun-arch-gen"
-version = "0.1.0-1.19.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e903977e89dbb2f77008307ce9953281f681a099e647b79e9220169278ce1970"
-
-[[package]]
-name = "krun-cpuid"
-version = "0.1.0-1.19.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69e0bda6161895cc919dff67cf5d51d03085a93e2f2022aa52da406d758a566a"
-dependencies = [
- "kvm-bindings",
- "kvm-ioctls",
- "vmm-sys-util 0.14.0",
-]
-
-[[package]]
-name = "krun-devices"
-version = "0.1.0-1.19.3"
-dependencies = [
- "bitflags 1.3.2",
- "caps",
- "crossbeam-channel",
- "imago",
- "krun-arch",
- "krun-hvf",
- "krun-polly",
- "krun-utils",
- "kvm-bindings",
- "kvm-ioctls",
- "libc",
- "libloading",
- "log",
- "lru 0.12.5",
- "nix 0.30.1",
- "persisting-overlay-core",
- "rand 0.9.4",
- "virtio-bindings",
- "vm-fdt",
- "vm-memory",
-]
-
-[[package]]
-name = "krun-hvf"
-version = "0.1.0-1.19.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f7e78f0c5431195ca36aded1886024872699a6a56f0a600f619aeb7ef2161bc"
-dependencies = [
- "crossbeam-channel",
- "krun-arch",
- "libloading",
- "log",
-]
-
-[[package]]
-name = "krun-init-blob"
-version = "0.1.0-1.19.3"
-
-[[package]]
-name = "krun-kernel"
-version = "0.1.0-1.19.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e48953b0f707aafee40684fdf45fcb0ea068745aaa9500f672c1a39da113e97a"
-dependencies = [
- "krun-utils",
- "vm-memory",
-]
-
-[[package]]
-name = "krun-polly"
-version = "0.1.0-1.19.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d2c61890357072d5751ef813aea744b93a67bfc3b820f36061f9706f72af84d"
-dependencies = [
- "krun-utils",
- "libc",
-]
-
-[[package]]
-name = "krun-smbios"
-version = "0.1.0-1.19.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01462ad735097a9a9650564e7f7ba082db720a41696f94ec9fb56ecaf072c744"
-dependencies = [
- "vm-memory",
-]
-
-[[package]]
-name = "krun-utils"
-version = "0.1.0-1.19.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8315551f3fd0c86191ff14318ac595a1e62d9c1e0690d30355d3a4e0ac741c87"
-dependencies = [
- "bitflags 1.3.2",
- "crossbeam-channel",
- "kvm-bindings",
- "libc",
- "log",
- "nix 0.30.1",
- "vmm-sys-util 0.14.0",
-]
-
-[[package]]
-name = "krun-vmm"
-version = "0.1.0-1.19.3"
-dependencies = [
- "bzip2",
- "crossbeam-channel",
- "flate2",
- "krun-arch",
- "krun-arch-gen",
- "krun-cpuid",
- "krun-devices",
- "krun-hvf",
- "krun-kernel",
- "krun-polly",
- "krun-utils",
- "kvm-bindings",
- "kvm-ioctls",
- "libc",
- "linux-loader",
- "log",
- "nix 0.30.1",
- "vm-memory",
- "vmm-sys-util 0.15.0",
- "zstd",
-]
-
-[[package]]
-name = "kstring"
-version = "2.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b609e7ca5ea38f093c20a4a102335b247221c9643b7a6bc3510f196f99499a9e"
-dependencies = [
- "static_assertions",
-]
-
-[[package]]
-name = "kvm-bindings"
-version = "0.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a537873e15e8daabb416667e606d9b0abc2a8fb9a45bd5853b888ae0ead82f9"
-dependencies = [
- "vmm-sys-util 0.14.0",
-]
-
-[[package]]
-name = "kvm-ioctls"
-version = "0.22.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c8f7370330b4f57981e300fa39b02088f2f2a5c2d0f1f994e8090589619c56d"
-dependencies = [
- "bitflags 2.11.1",
- "kvm-bindings",
- "libc",
- "vmm-sys-util 0.14.0",
-]
-
[[package]]
name = "lance"
version = "11.0.0"
@@ -5702,37 +4547,6 @@ version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
-[[package]]
-name = "libkrun"
-version = "1.19.3"
-dependencies = [
- "crossbeam-channel",
- "env_logger",
- "krun-devices",
- "krun-hvf",
- "krun-init-blob",
- "krun-polly",
- "krun-utils",
- "krun-vmm",
- "kvm-bindings",
- "kvm-ioctls",
- "libc",
- "libloading",
- "log",
- "once_cell",
- "vm-memory",
-]
-
-[[package]]
-name = "libloading"
-version = "0.8.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
-dependencies = [
- "cfg-if 1.0.4",
- "windows-link",
-]
-
[[package]]
name = "libm"
version = "0.2.16"
@@ -5769,15 +4583,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "348d0075b1fc163b26d72a7f75fc5141daf2fd1bdf128d873cbaf6785d495bdf"
-[[package]]
-name = "linux-loader"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de72cb02c55ecffcf75fe78295926f872eb6eb0a58d629c58a8c324dc26380f6"
-dependencies = [
- "vm-memory",
-]
-
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
@@ -5942,12 +4747,6 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d"
-[[package]]
-name = "maplit"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
-
[[package]]
name = "matchers"
version = "0.2.0"
@@ -5976,17 +4775,6 @@ dependencies = [
"thread-tree",
]
-[[package]]
-name = "maybe-async"
-version = "0.2.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "md-5"
version = "0.10.6"
@@ -6032,15 +4820,6 @@ 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"
@@ -6130,46 +4909,21 @@ checksum = "1fafa6961cabd9c63bcd77a45d7e3b7f3b552b70417831fb0f56db717e72407e"
name = "multimap"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
-
-[[package]]
-name = "ndarray"
-version = "0.16.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841"
-dependencies = [
- "matrixmultiply",
- "num-complex",
- "num-integer",
- "num-traits",
- "portable-atomic",
- "portable-atomic-util",
- "rawpointer",
-]
-
-[[package]]
-name = "nix"
-version = "0.29.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
-dependencies = [
- "bitflags 2.11.1",
- "cfg-if 1.0.4",
- "cfg_aliases",
- "libc",
-]
+checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
[[package]]
-name = "nix"
-version = "0.30.1"
+name = "ndarray"
+version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
+checksum = "882ed72dce9365842bf196bdeedf5055305f11fc8c03dee7bb0194a6cad34841"
dependencies = [
- "bitflags 2.11.1",
- "cfg-if 1.0.4",
- "cfg_aliases",
- "libc",
- "memoffset",
+ "matrixmultiply",
+ "num-complex",
+ "num-integer",
+ "num-traits",
+ "portable-atomic",
+ "portable-atomic-util",
+ "rawpointer",
]
[[package]]
@@ -6181,12 +4935,6 @@ dependencies = [
"memchr",
]
-[[package]]
-name = "nonempty"
-version = "0.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9737e026353e5cd0736f98eddae28665118eb6f6600902a7f50db585621fecb6"
-
[[package]]
name = "ntapi"
version = "0.4.3"
@@ -7114,13 +5862,9 @@ version = "0.3.0"
dependencies = [
"anyhow",
"ipnet",
- "libc",
"proptest",
"serde",
"serde_json",
- "tempfile",
- "tokio",
- "tokio-util",
]
[[package]]
@@ -7197,34 +5941,6 @@ dependencies = [
"url",
]
-[[package]]
-name = "persisting-overlay-core"
-version = "0.3.0"
-dependencies = [
- "libc",
- "log",
- "serde",
- "serde_json",
- "sha2 0.11.0",
- "tempfile",
-]
-
-[[package]]
-name = "persisting-overlayfs"
-version = "0.3.0"
-dependencies = [
- "anyhow",
- "clap",
- "env_logger",
- "fuser",
- "jj-lib",
- "libc",
- "log",
- "persisting-overlay-core",
- "pollster",
- "tempfile",
-]
-
[[package]]
name = "persisting-overlaynet"
version = "0.3.0"
@@ -7324,131 +6040,6 @@ dependencies = [
"uuid",
]
-[[package]]
-name = "persisting-ppilot"
-version = "0.3.0"
-dependencies = [
- "anyhow",
- "async-trait",
- "chrono",
- "clap",
- "futures",
- "persisting-agentctl",
- "persisting-events",
- "proptest",
- "pulsing-actor",
- "serde",
- "serde_json",
- "sha2 0.11.0",
- "tempfile",
- "tokio",
- "tokio-stream",
- "tokio-util",
- "tracing",
- "tracing-subscriber",
- "uuid",
-]
-
-[[package]]
-name = "persisting-pvisor"
-version = "0.3.0"
-dependencies = [
- "anyhow",
- "async-trait",
- "chrono",
- "clap",
- "dirs",
- "flate2",
- "fs2",
- "globset",
- "libc",
- "libkrun",
- "persisting-agentctl",
- "persisting-events",
- "persisting-gateway",
- "persisting-overlay-core",
- "persisting-overlayfs",
- "persisting-overlaynet",
- "persisting-ppilot",
- "persisting-replay",
- "proptest",
- "reqwest 0.12.28",
- "serde",
- "serde_json",
- "sha2 0.11.0",
- "tar",
- "tempfile",
- "thiserror 2.0.18",
- "tokio",
- "tokio-util",
- "toml",
- "tracing",
- "tracing-subscriber",
- "uuid",
- "zstd",
-]
-
-[[package]]
-name = "persisting-replay"
-version = "0.3.0"
-dependencies = [
- "anyhow",
- "axum",
- "chrono",
- "fs2",
- "libc",
- "reqwest 0.12.28",
- "serde",
- "serde_json",
- "sha2 0.11.0",
- "tempfile",
- "tokio",
- "toml",
- "uuid",
-]
-
-[[package]]
-name = "pest"
-version = "2.8.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2"
-dependencies = [
- "memchr",
- "ucd-trie",
-]
-
-[[package]]
-name = "pest_derive"
-version = "2.8.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd"
-dependencies = [
- "pest",
- "pest_generator",
-]
-
-[[package]]
-name = "pest_generator"
-version = "2.8.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6"
-dependencies = [
- "pest",
- "pest_meta",
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "pest_meta"
-version = "2.8.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c"
-dependencies = [
- "pest",
-]
-
[[package]]
name = "petgraph"
version = "0.8.3"
@@ -7622,12 +6213,6 @@ dependencies = [
"plotters-backend",
]
-[[package]]
-name = "pollster"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3"
-
[[package]]
name = "portable-atomic"
version = "1.13.1"
@@ -7708,15 +6293,6 @@ dependencies = [
"unicode-ident",
]
-[[package]]
-name = "prodash"
-version = "31.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "962200e2d7d551451297d9fdce85138374019ada198e30ea9ede38034e27604c"
-dependencies = [
- "parking_lot",
-]
-
[[package]]
name = "proptest"
version = "1.11.0"
@@ -7842,7 +6418,7 @@ checksum = "8a751c49db41d362084cb011c77391c1b0e857d11f4e6a839f6452c722a090a6"
dependencies = [
"anyhow",
"async-trait",
- "bincode 1.3.3",
+ "bincode",
"bytes",
"dashmap",
"futures",
@@ -8039,16 +6615,6 @@ dependencies = [
"rand_core 0.9.5",
]
-[[package]]
-name = "rand_chacha"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e6af7f3e25ded52c41df4e0b1af2d047e45896c2f3281792ed68a1c243daedb"
-dependencies = [
- "ppv-lite86",
- "rand_core 0.10.1",
-]
-
[[package]]
name = "rand_core"
version = "0.6.4"
@@ -8855,15 +7421,6 @@ dependencies = [
"serde",
]
-[[package]]
-name = "serde_spanned"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
-dependencies = [
- "serde_core",
-]
-
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@@ -8943,16 +7500,6 @@ dependencies = [
"digest 0.11.3",
]
-[[package]]
-name = "sha1-checked"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423"
-dependencies = [
- "digest 0.10.7",
- "sha1 0.10.6",
-]
-
[[package]]
name = "sha1_smol"
version = "1.0.1"
@@ -9000,12 +7547,6 @@ dependencies = [
"lazy_static",
]
-[[package]]
-name = "shell-words"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77"
-
[[package]]
name = "shellexpand"
version = "3.1.2"
@@ -9082,9 +7623,6 @@ name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
-dependencies = [
- "serde",
-]
[[package]]
name = "smoltcp"
@@ -9326,17 +7864,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
-[[package]]
-name = "tar"
-version = "0.4.46"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840"
-dependencies = [
- "filetime",
- "libc",
- "xattr",
-]
-
[[package]]
name = "tempfile"
version = "3.27.0"
@@ -9565,7 +8092,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
- "serde_spanned 0.6.9",
+ "serde_spanned",
"toml_datetime 0.6.11",
"toml_edit 0.22.27",
]
@@ -9596,7 +8123,7 @@ checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap 2.14.0",
"serde",
- "serde_spanned 0.6.9",
+ "serde_spanned",
"toml_datetime 0.6.11",
"toml_write",
"winnow 0.7.15",
@@ -9609,11 +8136,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
dependencies = [
"indexmap 2.14.0",
- "serde_core",
- "serde_spanned 1.1.1",
"toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
- "toml_writer",
"winnow 1.0.2",
]
@@ -9632,12 +8156,6 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
-[[package]]
-name = "toml_writer"
-version = "1.1.2+spec-1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
-
[[package]]
name = "tonic"
version = "0.14.6"
@@ -9864,21 +8382,6 @@ version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "214ca0b2191785cbc06209b9ca1861e048e39b5ba33574b3cedd58363d5bb5f6"
-[[package]]
-name = "ucd-trie"
-version = "0.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
-
-[[package]]
-name = "uluru"
-version = "3.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c8a2469e56e6e5095c82ccd3afb98dad95f7af7929aab6d8ba8d6e0f73657da"
-dependencies = [
- "arrayvec",
-]
-
[[package]]
name = "unarray"
version = "0.1.4"
@@ -9891,12 +8394,6 @@ version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
-[[package]]
-name = "unicode-bom"
-version = "2.0.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217"
-
[[package]]
name = "unicode-ident"
version = "1.0.24"
@@ -9948,12 +8445,6 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
-[[package]]
-name = "unty"
-version = "0.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae"
-
[[package]]
name = "url"
version = "2.5.8"
@@ -10016,55 +8507,6 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
-[[package]]
-name = "virtio-bindings"
-version = "0.2.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "091f1f09cfbf2a78563b562e7a949465cce1aef63b6065645188d995162f8868"
-
-[[package]]
-name = "virtue"
-version = "0.0.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1"
-
-[[package]]
-name = "vm-fdt"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e21282841a059bb62627ce8441c491f09603622cd5a21c43bfedc85a2952f23"
-
-[[package]]
-name = "vm-memory"
-version = "0.17.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f39348a049689cabd3377cdd9182bf526ec76a6f823b79903896452e9d7a7380"
-dependencies = [
- "libc",
- "thiserror 2.0.18",
- "winapi",
-]
-
-[[package]]
-name = "vmm-sys-util"
-version = "0.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d21f366bf22bfba3e868349978766a965cbe628c323d58e026be80b8357ab789"
-dependencies = [
- "bitflags 1.3.2",
- "libc",
-]
-
-[[package]]
-name = "vmm-sys-util"
-version = "0.15.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "506c62fdf617a5176827c2f9afbcf1be155b03a9b4bf9617a60dbc07e3a1642f"
-dependencies = [
- "bitflags 1.3.2",
- "libc",
-]
-
[[package]]
name = "vsimd"
version = "0.8.0"
@@ -10559,16 +9001,6 @@ dependencies = [
"memchr",
]
-[[package]]
-name = "winreg"
-version = "0.56.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d6f32a0ff4a9f6f01231eb2059cc85479330739333e0e58cadf03b6af2cca10"
-dependencies = [
- "cfg-if 1.0.4",
- "windows-sys 0.61.2",
-]
-
[[package]]
name = "wit-bindgen"
version = "0.51.0"
@@ -10977,12 +9409,6 @@ dependencies = [
"syn",
]
-[[package]]
-name = "zlib-rs"
-version = "0.6.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5"
-
[[package]]
name = "zmij"
version = "1.0.21"
diff --git a/Cargo.toml b/Cargo.toml
index 480791773..1078f8258 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,7 +3,7 @@ version = "0.3.0"
edition = "2024"
authors = ["reiase"]
license = "Apache-2.0"
-description = "Agent execution, orchestration, and durable history infrastructure"
+description = "Durable Agent trajectory capture, storage, and query infrastructure"
[workspace]
resolver = "3"
@@ -12,21 +12,13 @@ members = [
"crates/persisting-events",
"crates/persisting-gateway",
"crates/persisting-dlcapt",
- "crates/persisting-ppilot",
- "crates/persisting-pvisor",
- "crates/persisting-replay",
"crates/persisting-pchronicle",
"crates/persisting-pchronicle-cli",
- "crates/persisting-overlayfs",
- "crates/persisting-overlay-core",
"crates/persisting-overlaynet",
]
-exclude = ["vendor/libkrun", "vendor/krun-vmm", "vendor/krun-devices"]
default-members = [
"crates/persisting-pchronicle-cli",
- "crates/persisting-ppilot",
- "crates/persisting-pvisor",
]
[workspace.dependencies]
@@ -41,19 +33,14 @@ clap = "4"
dashmap = "6"
datafusion = { version = "54.1.0", default-features = false }
dirs = "6"
-env_logger = "0.11"
fs2 = "0.4"
-flate2 = "1"
-fuser = { version = "0.15", default-features = false }
futures = "0.3"
futures-util = { version = "0.3", default-features = false }
-globset = "0.4"
hostname = "0.4"
http-body-util = "0.1"
hyper = "1"
hyper-util = { version = "0.1", default-features = false }
ipnet = "2"
-jj-lib = "0.43.0"
lance = { version = "11.0.0", default-features = false }
lance-arrow = "11.0.0"
lance-core = "11.0.0"
@@ -64,8 +51,6 @@ lance-io = "11.0.0"
lance-linalg = "11.0.0"
lance-table = "11.0.0"
libc = "0.2"
-libkrun = "=1.19.3"
-log = "0.4"
opendal = { version = "0.57.0", default-features = true, features = [
"services-azblob",
"services-cos",
@@ -80,13 +65,8 @@ object_store = "0.13.2"
persisting-agentctl = { path = "crates/persisting-agentctl" }
persisting-events = { path = "crates/persisting-events" }
persisting-gateway = { path = "crates/persisting-gateway" }
-persisting-overlayfs = { path = "crates/persisting-overlayfs", default-features = false }
-persisting-overlay-core = { path = "crates/persisting-overlay-core" }
persisting-overlaynet = { path = "crates/persisting-overlaynet" }
persisting-pchronicle = { path = "crates/persisting-pchronicle", default-features = false }
-persisting-pvisor = { path = "crates/persisting-pvisor" }
-persisting-replay = { path = "crates/persisting-replay" }
-pollster = "0.4"
pulsing-actor = { version = "0.1.2", default-features = false }
proptest = "1"
regex = "1"
@@ -94,9 +74,7 @@ reqwest = { version = "0.12", default-features = false }
serde = "1"
serde_json = "1"
serde_yaml = "0.9"
-sha2 = "0.11"
smoltcp = { version = "0.13.1", default-features = false }
-tar = "0.4"
tempfile = "3"
thiserror = "2"
tokio = { version = "1", default-features = false }
@@ -155,8 +133,3 @@ panic = "abort"
# adapter preserves Prost's code generation API but parses .proto files with
# pure-Rust protox, avoiding both a system protoc and vendored protobuf-src.
prost-build = { path = "vendor/prost-build-protox" }
-fuser = { path = "vendor/fuser" }
-libkrun = { path = "vendor/libkrun" }
-krun-init-blob = { path = "vendor/krun-init-blob" }
-krun-vmm = { path = "vendor/krun-vmm" }
-krun-devices = { path = "vendor/krun-devices" }
diff --git a/NOTICE b/NOTICE
index e822d6d05..92fc7ae05 100644
--- a/NOTICE
+++ b/NOTICE
@@ -9,28 +9,6 @@ Version 2.0. See the repository root `LICENSE` file.
Third-party notices
===================
-fuser
------
-
-`vendor/fuser` contains a modified copy of the fuser project:
-
-https://github.com/cberner/fuser
-
-Copyright its authors and contributors. It is distributed under the MIT
-License; see `vendor/fuser/LICENSE.md`.
-
-libkrun, krun-vmm, and krun-devices
------------------------------------
-
-`vendor/libkrun`, `vendor/krun-vmm`, and `vendor/krun-devices` contain pinned,
-modified copies of libkrun 1.19.3 and its VMM/device crates:
-
-https://github.com/containers/libkrun
-
-Copyright Red Hat, Inc. and the upstream authors and contributors. They are
-distributed under the Apache License, Version 2.0. Persisting's modifications
-add the mountless copy-on-write virtio-fs backend used by pVisor.
-
agentgateway test fixtures
--------------------------
diff --git a/README.md b/README.md
index 6e13fe48a..eddc01e8c 100644
--- a/README.md
+++ b/README.md
@@ -8,29 +8,15 @@
-Persisting connects durable model state—parameters and KV caches—with durable
-Agent history—trajectories and execution records. The current product is the
-path from execution to queryable history:
+Persisting contains **pChronicle**, which captures, browses, queries, exchanges,
+and serves durable Agent trajectory Datasets.
-| Governed execution | Durable history |
-| --- | --- |
-|
|
|
-
-
-- **`pvisor`** is an executor that produces persistable, reviewable facts:
- staged Effects and execution records from one Agent Run;
-- **`pchronicle`** browses, queries, exchanges, and serves trajectory Datasets.
-
-Each command works on its own. Connected, they cover `pvisor run --stage …` →
-review/apply → configured capture → a queryable Dataset.
-
-
+
## Install
```bash
pip install persisting[lance]
-pvisor --version
pchronicle --version
```
@@ -41,21 +27,7 @@ curl -fsSL https://raw.githubusercontent.com/DeepLink-org/Persisting/main/script
```
See the [installation guide](https://deeplink-org.github.io/Persisting/installation/)
-for platform requirements and executor setup.
-
-## Run one Agent and review its changes
-
-```bash
-pvisor run --stage ./runs/task-001 -- codex
-pvisor review last
-pvisor apply last --all # or: pvisor drop last
-```
-
-`--stage` creates a copy-on-write workspace view for review; without it the
-Agent may write the real project tree. The exact boundary is
-platform-dependent and recorded with the Run—consult the
-[execution guide](https://deeplink-org.github.io/Persisting/pvisor/guides/execution/)
-before treating it as a security boundary.
+for supported platforms and setup.
## Query Agent trajectory history
@@ -69,25 +41,22 @@ The onboarding flow creates a temporary example Dataset—no source checkout
required. `pchronicle import` accepts ATIF, ACTF, and OpenAI Messages;
`pchronicle serve` starts a loopback-only, read-only Dataset UI and API.
-After capture is configured, selected pVisor Run events can enter a pChronicle
-Dataset. See the [capture guide](https://deeplink-org.github.io/Persisting/pvisor/guides/capture/).
+Gateway capture can write trajectory events into pChronicle Datasets. See the
+[Gateway guide](https://deeplink-org.github.io/Persisting/pchronicle/guides/serve-gateway/).
## Current maturity
| Capability | Status |
|---|---|
-| pVisor host execution, review, checkpoints, and transactional workspace | Implemented |
| pChronicle local/S3 catalog, bounded SQL, analysis, find, import/export | Implemented |
| pChronicle loopback-only read API and embedded Web UI | Implemented |
| Gateway capture and cooperative proxy policy | Implemented |
-| Container/libkrun executors and transparent network boundaries | Platform-dependent; see the pVisor and OverlayNet docs |
| Queue and document Search | Separate stable capabilities |
| Tensor Memory / TTAS | Experimental |
## Documentation
- [Choose a workflow](https://deeplink-org.github.io/Persisting/overview/) — pick the entry point that matches your task
-- [Run your first Agent](https://deeplink-org.github.io/Persisting/pvisor/get-started/) — the run-review-apply loop
- [Explore durable history](https://deeplink-org.github.io/Persisting/pchronicle/get-started/) — browse and query a trajectory Dataset
- [Project architecture](https://deeplink-org.github.io/Persisting/system-design/) — ownership and delivery boundaries
diff --git a/benchmark/README.md b/benchmark/README.md
index b340074e7..aea55dedf 100644
--- a/benchmark/README.md
+++ b/benchmark/README.md
@@ -1,6 +1,6 @@
# Persisting benchmarks
-**仓库级黑盒与微基准入口:Gateway、pChronicle、pVisor,以及独立的 Queue 吞吐压测。**
+**仓库级黑盒与微基准入口:Gateway、pChronicle,以及独立的 Queue 吞吐压测。**
拥有可复现的压测脚本与报告契约。不拥有被测组件的产品行为;Queue 子系统的语义以
Queue 文档为准,这里只保留既有压测入口。
@@ -34,20 +34,6 @@ just benchmark-pchronicle-compare \
详见 [`pchronicle/`](pchronicle/README.md)。
-## pVisor
-
-进程启动与 durable Run Bundle 访问基准:
-
-```bash
-just benchmark-pvisor
-just benchmark-pvisor nightly target/pvisor-benchmark/nightly
-just benchmark-pvisor-compare \
- target/pvisor-benchmark/candidate/raw-report.json \
- target/pvisor-benchmark/main/raw-report.json
-```
-
-详见 [`pvisor/`](pvisor/README.md)。
-
## Queue
本目录用于对 Persisting Queue 做吞吐压测,可配置生产者/消费者数量,测试极限吞吐。
@@ -102,6 +88,4 @@ python benchmark/throughput_stress.py -p 8 -c 8 -d 60 -b 100 --warmup 2
## Links
-- [Gateway architecture](../docs/src/pvisor/design/gateway.md)
-- [pChronicle design](../docs/src/pchronicle/design/index.md)
-- [pVisor design](../docs/src/pvisor/design/index.md)
+- [pChronicle design](../docs/src/en/pchronicle/design/index.md)
diff --git a/benchmark/gateway/README.md b/benchmark/gateway/README.md
index adeab65a1..3d85af107 100644
--- a/benchmark/gateway/README.md
+++ b/benchmark/gateway/README.md
@@ -115,6 +115,6 @@ just benchmark-gateway-replay examples/data /tmp/gateway-replay-review
## Links
-- [Gateway architecture](../../docs/src/pvisor/design/gateway.md)
+- [Gateway architecture](../../docs/src/en/pchronicle/guides/serve-gateway.md)
- [`persisting-gateway`](../../crates/persisting-gateway/README.md)
- [Regression tests](../../tests/regression/README.md)
diff --git a/benchmark/pvisor/README.md b/benchmark/pvisor/README.md
deleted file mode 100644
index 1a74a1261..000000000
--- a/benchmark/pvisor/README.md
+++ /dev/null
@@ -1,41 +0,0 @@
-# pVisor benchmark
-
-**Measures the process-level cost of a minimal host Run and of reading its
-durable Run Bundle through `status --json` and `review --json`.**
-
-Owns the smoke / nightly suites and the `pvisor-benchmark/v1` report schema.
-Does not own Run lifecycle or isolation backends. Every sampled Run must finish
-successfully and produce a completed, zero-exit-code bundle; a fast but
-incomplete Run is rejected.
-
-## Run
-
-```bash
-just benchmark-pvisor
-just benchmark-pvisor nightly target/pvisor-benchmark/nightly
-
-just benchmark-pvisor-compare \
- target/pvisor-benchmark/candidate/raw-report.json \
- target/pvisor-benchmark/main/raw-report.json
-```
-
-`just benchmark-pvisor-compare` takes candidate then optional baseline. The
-`smoke` suite uses 2 warmups and 10 samples for pull-request feedback. The
-`nightly` suite uses 10 warmups and 50 samples for a more stable distribution.
-Both write the same `pvisor-benchmark/v1` raw schema and Markdown report.
-
-Baseline and candidate measurements are meaningful only when collected on the
-same host with the same suite. Comparison marks a metric as a regression when
-it crosses the configurable threshold (15% by default); the report is
-informational unless the runner is explicitly given `--fail-on-regression`.
-
-Unit-test the report contract without running the suite:
-
-```bash
-just test-pvisor-benchmark
-```
-
-## Links
-
-- [pVisor design](../../docs/src/pvisor/design/index.md)
-- [`persisting-pvisor`](../../crates/persisting-pvisor/README.md)
diff --git a/benchmark/pvisor/bench.py b/benchmark/pvisor/bench.py
deleted file mode 100755
index adecdb242..000000000
--- a/benchmark/pvisor/bench.py
+++ /dev/null
@@ -1,545 +0,0 @@
-#!/usr/bin/env python3
-"""Run and compare the pVisor process-level benchmark suite."""
-
-from __future__ import annotations
-
-import argparse
-import datetime as dt
-import json
-import math
-import os
-import pathlib
-import platform
-import shlex
-import statistics
-import subprocess
-import sys
-import tempfile
-import time
-from typing import Any
-
-SCHEMA = "pvisor-benchmark/v1"
-SUITES = {
- "smoke": {"warmups": 2, "samples": 10},
- "nightly": {"warmups": 10, "samples": 50},
-}
-
-
-def run_command(
- command: list[str],
- *,
- cwd: pathlib.Path,
- env: dict[str, str] | None = None,
- log: pathlib.Path | None = None,
-) -> subprocess.CompletedProcess[str]:
- print(f"==> {shlex.join(command)}", flush=True)
- completed = subprocess.run(
- command,
- cwd=cwd,
- env=env,
- text=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- check=False,
- )
- if log is not None:
- log.parent.mkdir(parents=True, exist_ok=True)
- log.write_text(completed.stdout, encoding="utf-8")
- if completed.returncode != 0:
- sys.stderr.write(completed.stdout)
- raise RuntimeError(
- f"command failed with exit code {completed.returncode}: {shlex.join(command)}"
- )
- return completed
-
-
-def command_output(command: list[str], cwd: pathlib.Path) -> str:
- return run_command(command, cwd=cwd).stdout.strip()
-
-
-def resolve_path(value: str, repo: pathlib.Path) -> pathlib.Path:
- path = pathlib.Path(value)
- return path.resolve() if path.is_absolute() else (repo / path).resolve()
-
-
-def cpu_name() -> str:
- cpuinfo = pathlib.Path("/proc/cpuinfo")
- if cpuinfo.is_file():
- for line in cpuinfo.read_text(encoding="utf-8").splitlines():
- if line.lower().startswith("model name"):
- return line.split(":", 1)[1].strip()
- return platform.processor() or "unknown"
-
-
-def environment(repo: pathlib.Path, suite: str) -> dict[str, Any]:
- dirty = bool(command_output(["git", "status", "--porcelain"], repo))
- return {
- "suite": suite,
- "git_commit": command_output(["git", "rev-parse", "HEAD"], repo),
- "git_dirty": dirty,
- "recorded_at": dt.datetime.now(dt.timezone.utc).isoformat(),
- "os": platform.system().lower(),
- "os_release": platform.release(),
- "arch": platform.machine(),
- "cpu": cpu_name(),
- "logical_cpus": os.cpu_count(),
- "rustc": command_output(["rustc", "--version"], repo),
- "cargo": command_output(["cargo", "--version"], repo),
- "python": platform.python_version(),
- }
-
-
-def percentile(values: list[float], percent: float) -> float:
- if not values:
- raise ValueError("cannot calculate a percentile without samples")
- ordered = sorted(values)
- position = (len(ordered) - 1) * percent / 100
- lower = math.floor(position)
- upper = math.ceil(position)
- if lower == upper:
- return ordered[lower]
- weight = position - lower
- return ordered[lower] * (1 - weight) + ordered[upper] * weight
-
-
-def distribution(values: list[float]) -> dict[str, Any]:
- return {
- "samples": len(values),
- "raw_ms": [round(value, 6) for value in values],
- "min_ms": round(min(values), 6),
- "mean_ms": round(statistics.fmean(values), 6),
- "p50_ms": round(percentile(values, 50), 6),
- "p95_ms": round(percentile(values, 95), 6),
- "p99_ms": round(percentile(values, 99), 6),
- "max_ms": round(max(values), 6),
- "stdev_ms": round(statistics.pstdev(values), 6),
- }
-
-
-def measurement(value: float, unit: str, direction: str, source: str) -> dict[str, Any]:
- return {
- "value": round(value, 6),
- "unit": unit,
- "direction": direction,
- "source": source,
- }
-
-
-def timed_command(
- command: list[str],
- *,
- cwd: pathlib.Path,
- env: dict[str, str],
- capture_json: bool = False,
-) -> tuple[float, dict[str, Any] | None]:
- stdout: int | None = subprocess.PIPE if capture_json else subprocess.DEVNULL
- started = time.perf_counter_ns()
- completed = subprocess.run(
- command,
- cwd=cwd,
- env=env,
- stdin=subprocess.DEVNULL,
- stdout=stdout,
- stderr=subprocess.PIPE,
- check=False,
- )
- elapsed_ms = (time.perf_counter_ns() - started) / 1_000_000
- if completed.returncode != 0:
- stderr = completed.stderr.decode(errors="replace") if completed.stderr else ""
- raise RuntimeError(
- f"benchmark command failed ({completed.returncode}): {shlex.join(command)}\n{stderr}"
- )
- document = None
- if capture_json:
- try:
- document = json.loads(completed.stdout)
- except json.JSONDecodeError as error:
- raise RuntimeError(
- f"benchmark command did not emit JSON: {shlex.join(command)}"
- ) from error
- return elapsed_ms, document
-
-
-def run_direct(binary: pathlib.Path, cwd: pathlib.Path, env: dict[str, str]) -> float:
- elapsed, _ = timed_command([str(binary)], cwd=cwd, env=env)
- return elapsed
-
-
-def run_pvisor(
- pvisor: pathlib.Path,
- cwd: pathlib.Path,
- run_home: pathlib.Path,
- env: dict[str, str],
-) -> tuple[float, pathlib.Path]:
- before = set(run_home.glob("run-*")) if run_home.exists() else set()
- elapsed, _ = timed_command(
- [str(pvisor), "run", "--stdio", "capture", "--", "/usr/bin/true"],
- cwd=cwd,
- env=env,
- )
- created = sorted(set(run_home.glob("run-*")) - before)
- if len(created) != 1:
- raise RuntimeError(f"expected one pVisor Run, found {len(created)}")
- validate_bundle(created[0] / "run-bundle.json")
- return elapsed, created[0]
-
-
-def validate_bundle(path: pathlib.Path) -> dict[str, Any]:
- document = json.loads(path.read_text(encoding="utf-8"))
- run = document.get("run", {})
- if run.get("state") != "completed" or run.get("exit_code") != 0:
- raise RuntimeError(f"pVisor benchmark produced an invalid Run Bundle: {path}")
- return document
-
-
-def sample_json_command(
- command: list[str],
- *,
- cwd: pathlib.Path,
- env: dict[str, str],
- expected_state: str,
-) -> float:
- elapsed, document = timed_command(command, cwd=cwd, env=env, capture_json=True)
- assert document is not None
- if document.get("run", {}).get("state") != expected_state:
- raise RuntimeError(f"unexpected Run state from {shlex.join(command)}")
- return elapsed
-
-
-def benchmark(
- repo: pathlib.Path,
- output: pathlib.Path,
- target_dir: pathlib.Path,
- suite: str,
-) -> dict[str, Any]:
- config = SUITES[suite]
- output.mkdir(parents=True, exist_ok=True)
- target_dir.mkdir(parents=True, exist_ok=True)
- build_env = os.environ.copy()
- build_env["CARGO_TARGET_DIR"] = str(target_dir)
- run_command(
- [
- "cargo",
- "build",
- "--release",
- "--locked",
- "-p",
- "persisting-pvisor",
- "--bin",
- "pvisor",
- ],
- cwd=repo,
- env=build_env,
- log=output / "logs" / "build.log",
- )
- pvisor = target_dir / "release" / "pvisor"
- direct_binary = pathlib.Path("/usr/bin/true")
- if not pvisor.is_file() or not direct_binary.is_file():
- raise RuntimeError("benchmark binaries are unavailable")
-
- with tempfile.TemporaryDirectory(prefix="pvisor-benchmark-") as temporary:
- work = pathlib.Path(temporary)
- workspace = work / "workspace"
- run_home = work / "runs"
- workspace.mkdir()
- run_env = os.environ.copy()
- run_env["PERSISTING_RUN_HOME"] = str(run_home)
-
- last_run: pathlib.Path | None = None
- for _ in range(config["warmups"]):
- run_direct(direct_binary, workspace, run_env)
- _, last_run = run_pvisor(pvisor, workspace, run_home, run_env)
-
- direct_samples: list[float] = []
- pvisor_samples: list[float] = []
- for _ in range(config["samples"]):
- direct_samples.append(run_direct(direct_binary, workspace, run_env))
- elapsed, last_run = run_pvisor(pvisor, workspace, run_home, run_env)
- pvisor_samples.append(elapsed)
-
- assert last_run is not None
- status_command = [str(pvisor), "status", "--json", str(last_run)]
- review_command = [str(pvisor), "review", "--json", str(last_run)]
- sample_json_command(
- status_command,
- cwd=workspace,
- env=run_env,
- expected_state="completed",
- )
- sample_json_command(
- review_command,
- cwd=workspace,
- env=run_env,
- expected_state="completed",
- )
- status_samples = [
- sample_json_command(
- status_command,
- cwd=workspace,
- env=run_env,
- expected_state="completed",
- )
- for _ in range(config["samples"])
- ]
- review_samples = [
- sample_json_command(
- review_command,
- cwd=workspace,
- env=run_env,
- expected_state="completed",
- )
- for _ in range(config["samples"])
- ]
- bundle_bytes = (last_run / "run-bundle.json").stat().st_size
-
- distributions = {
- "direct": distribution(direct_samples),
- "host_run": distribution(pvisor_samples),
- "status": distribution(status_samples),
- "review": distribution(review_samples),
- }
- direct_p50 = distributions["direct"]["p50_ms"]
- pvisor_p50 = distributions["host_run"]["p50_ms"]
- overhead = max(0.0, pvisor_p50 - direct_p50)
- ratio = pvisor_p50 / direct_p50 if direct_p50 else 0.0
- document = {
- "schema": SCHEMA,
- "environment": environment(repo, suite),
- "suite_config": config,
- "measurements": {
- "process": {
- "direct": {
- "latency_ms_p50": measurement(direct_p50, "ms", "lower", "wall-clock"),
- "latency_ms_p95": measurement(
- distributions["direct"]["p95_ms"],
- "ms",
- "lower",
- "wall-clock",
- ),
- },
- "host_run": {
- "latency_ms_p50": measurement(pvisor_p50, "ms", "lower", "wall-clock"),
- "latency_ms_p95": measurement(
- distributions["host_run"]["p95_ms"],
- "ms",
- "lower",
- "wall-clock",
- ),
- "overhead_ms_p50": measurement(overhead, "ms", "lower", "derived"),
- "overhead_ratio_p50": measurement(ratio, "x", "lower", "derived"),
- },
- },
- "bundle": {
- "status": {
- "latency_ms_p50": measurement(
- distributions["status"]["p50_ms"],
- "ms",
- "lower",
- "wall-clock",
- )
- },
- "review": {
- "latency_ms_p50": measurement(
- distributions["review"]["p50_ms"],
- "ms",
- "lower",
- "wall-clock",
- )
- },
- "run_bundle_bytes": measurement(
- float(bundle_bytes), "bytes", "lower", "filesystem"
- ),
- },
- },
- "distributions": distributions,
- }
- return document
-
-
-def measurement_items(document: dict[str, Any]) -> dict[str, dict[str, Any]]:
- found: dict[str, dict[str, Any]] = {}
-
- def visit(node: Any, segments: list[str]) -> None:
- if isinstance(node, dict) and {"value", "unit", "direction", "source"} <= set(node):
- found["/".join(segments)] = node
- return
- if not isinstance(node, dict):
- raise ValueError("measurement tree contains a non-measurement leaf")
- for key in sorted(node):
- visit(node[key], [*segments, key])
-
- visit(document["measurements"], [])
- return found
-
-
-def render_run_report(document: dict[str, Any]) -> str:
- environment_data = document["environment"]
- lines = [
- "# pVisor benchmark",
- "",
- f"- Suite: `{environment_data['suite']}`",
- f"- Commit: `{environment_data['git_commit']}`",
- f"- Host: `{environment_data['os']} {environment_data['arch']}` / "
- f"`{environment_data['cpu']}`",
- f"- Samples: `{document['suite_config']['samples']}` after "
- f"`{document['suite_config']['warmups']}` warmups",
- "",
- "| Metric | Value | Unit |",
- "|---|---:|---|",
- ]
- for path, item in measurement_items(document).items():
- lines.append(f"| `{path}` | {item['value']:.6g} | {item['unit']} |")
- lines.extend(
- [
- "",
- "Every measured `pvisor run` must exit successfully and produce a "
- "completed, zero-exit-code Run Bundle.",
- "",
- ]
- )
- return "\n".join(lines)
-
-
-def compare_reports(
- candidate: dict[str, Any],
- baseline: dict[str, Any] | None,
- threshold: float,
-) -> dict[str, Any]:
- if baseline is not None:
- baseline_environment = baseline["environment"]
- candidate_environment = candidate["environment"]
- for key in ("suite", "os", "arch", "cpu"):
- if baseline_environment.get(key) != candidate_environment.get(key):
- raise ValueError(
- f"cannot compare reports with different environment.{key}: "
- f"{baseline_environment.get(key)!r} != "
- f"{candidate_environment.get(key)!r}"
- )
- candidate_items = measurement_items(candidate)
- baseline_items = measurement_items(baseline) if baseline is not None else {}
- rows = []
- regressions = 0
- for path, current in candidate_items.items():
- previous = baseline_items.get(path)
- delta_pct = None
- status = "candidate-only"
- if previous is not None:
- if previous["unit"] != current["unit"]:
- raise ValueError(f"unit changed for {path}")
- baseline_value = float(previous["value"])
- candidate_value = float(current["value"])
- if baseline_value != 0:
- delta_pct = (candidate_value - baseline_value) / baseline_value * 100
- signed = delta_pct if current["direction"] == "lower" else -delta_pct
- if signed > threshold:
- status = "regression"
- regressions += 1
- elif signed < -threshold:
- status = "improvement"
- else:
- status = "stable"
- else:
- status = "stable" if candidate_value == 0 else "changed"
- rows.append(
- {
- "path": path,
- "baseline": previous["value"] if previous is not None else None,
- "candidate": current["value"],
- "unit": current["unit"],
- "delta_pct": round(delta_pct, 3) if delta_pct is not None else None,
- "status": status,
- }
- )
- return {
- "schema": "pvisor-benchmark-comparison/v1",
- "threshold_pct": threshold,
- "regressions": regressions,
- "baseline_commit": baseline["environment"]["git_commit"] if baseline is not None else None,
- "candidate_commit": candidate["environment"]["git_commit"],
- "metrics": rows,
- }
-
-
-def render_comparison(document: dict[str, Any]) -> str:
- lines = [
- "# pVisor benchmark comparison",
- "",
- f"Regression threshold: `{document['threshold_pct']:.3g}%`; "
- f"regressions: `{document['regressions']}`.",
- "",
- "| Metric | Baseline | Candidate | Delta | Status |",
- "|---|---:|---:|---:|---|",
- ]
- for row in document["metrics"]:
- baseline = "—" if row["baseline"] is None else f"{row['baseline']:.6g}"
- delta = "—" if row["delta_pct"] is None else f"{row['delta_pct']:+.2f}%"
- lines.append(
- f"| `{row['path']}` | {baseline} | {row['candidate']:.6g} "
- f"{row['unit']} | {delta} | {row['status']} |"
- )
- lines.append("")
- return "\n".join(lines)
-
-
-def load_report(path: pathlib.Path) -> dict[str, Any]:
- document = json.loads(path.read_text(encoding="utf-8"))
- if document.get("schema") != SCHEMA:
- raise ValueError(f"unsupported pVisor benchmark schema in {path}")
- measurement_items(document)
- return document
-
-
-def parse_args() -> argparse.Namespace:
- parser = argparse.ArgumentParser(description=__doc__)
- subparsers = parser.add_subparsers(dest="command", required=True)
-
- run_parser = subparsers.add_parser("run")
- run_parser.add_argument("--repo", default=str(pathlib.Path(__file__).parents[2]))
- run_parser.add_argument("--suite", choices=sorted(SUITES), default="smoke")
- run_parser.add_argument("--output", required=True)
- run_parser.add_argument("--target-dir", default="target/pvisor-benchmark-build")
-
- compare_parser = subparsers.add_parser("compare")
- compare_parser.add_argument("--repo", default=str(pathlib.Path(__file__).parents[2]))
- compare_parser.add_argument("--candidate", required=True)
- compare_parser.add_argument("--baseline", default="")
- compare_parser.add_argument("--output", required=True)
- compare_parser.add_argument("--regression-threshold", type=float, default=15.0)
- compare_parser.add_argument("--fail-on-regression", action="store_true")
- return parser.parse_args()
-
-
-def main() -> int:
- args = parse_args()
- repo = pathlib.Path(args.repo).resolve()
- output = resolve_path(args.output, repo)
- output.mkdir(parents=True, exist_ok=True)
- if args.command == "run":
- target_dir = resolve_path(args.target_dir, repo)
- document = benchmark(repo, output, target_dir, args.suite)
- (output / "raw-report.json").write_text(
- json.dumps(document, indent=2, sort_keys=True) + "\n", encoding="utf-8"
- )
- (output / "report.md").write_text(render_run_report(document), encoding="utf-8")
- print(f"pVisor benchmark report: {output / 'report.md'}")
- return 0
-
- candidate = load_report(resolve_path(args.candidate, repo))
- baseline = load_report(resolve_path(args.baseline, repo)) if args.baseline else None
- comparison = compare_reports(candidate, baseline, args.regression_threshold)
- (output / "comparison.json").write_text(
- json.dumps(comparison, indent=2, sort_keys=True) + "\n", encoding="utf-8"
- )
- (output / "report.md").write_text(render_comparison(comparison), encoding="utf-8")
- print(f"pVisor comparison report: {output / 'report.md'}")
- if args.fail_on_regression and comparison["regressions"]:
- return 1
- return 0
-
-
-if __name__ == "__main__":
- try:
- raise SystemExit(main())
- except (OSError, RuntimeError, ValueError) as error:
- print(f"error: {error}", file=sys.stderr)
- raise SystemExit(1) from error
diff --git a/benchmark/pvisor/run.sh b/benchmark/pvisor/run.sh
deleted file mode 100755
index 29a6469d1..000000000
--- a/benchmark/pvisor/run.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-set -euo pipefail
-
-script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
-repo_root="$(cd -- "$script_dir/../.." && pwd)"
-
-exec python3 "$script_dir/bench.py" "$@" --repo "$repo_root"
diff --git a/benchmark/pvisor/test_bench.py b/benchmark/pvisor/test_bench.py
deleted file mode 100755
index d103d39b2..000000000
--- a/benchmark/pvisor/test_bench.py
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env python3
-from __future__ import annotations
-
-import importlib.util
-import pathlib
-import unittest
-
-MODULE_PATH = pathlib.Path(__file__).with_name("bench.py")
-SPEC = importlib.util.spec_from_file_location("pvisor_bench", MODULE_PATH)
-assert SPEC is not None and SPEC.loader is not None
-BENCH = importlib.util.module_from_spec(SPEC)
-SPEC.loader.exec_module(BENCH)
-
-
-def report(value: float) -> dict:
- return {
- "schema": BENCH.SCHEMA,
- "environment": {
- "git_commit": "commit",
- "suite": "smoke",
- "os": "linux",
- "arch": "x86_64",
- "cpu": "test-cpu",
- },
- "measurements": {
- "process": {
- "host_run": {
- "latency_ms_p50": BENCH.measurement(value, "ms", "lower", "wall-clock")
- }
- }
- },
- }
-
-
-class PVisorBenchmarkTests(unittest.TestCase):
- def test_percentile_interpolates(self) -> None:
- self.assertEqual(BENCH.percentile([1.0, 2.0, 3.0, 4.0], 50), 2.5)
- self.assertAlmostEqual(BENCH.percentile([1.0, 2.0], 95), 1.95)
-
- def test_comparison_classifies_lower_is_better(self) -> None:
- comparison = BENCH.compare_reports(report(120), report(100), 15)
- self.assertEqual(comparison["regressions"], 1)
- self.assertEqual(comparison["metrics"][0]["status"], "regression")
-
- def test_candidate_only_report_is_supported(self) -> None:
- comparison = BENCH.compare_reports(report(100), None, 15)
- self.assertEqual(comparison["regressions"], 0)
- self.assertEqual(comparison["metrics"][0]["status"], "candidate-only")
-
- def test_different_suites_cannot_be_compared(self) -> None:
- candidate = report(100)
- baseline = report(100)
- baseline["environment"]["suite"] = "nightly"
- with self.assertRaisesRegex(ValueError, "environment.suite"):
- BENCH.compare_reports(candidate, baseline, 15)
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/crates/persisting-agentctl/Cargo.toml b/crates/persisting-agentctl/Cargo.toml
index b89a62b1f..8d1f8b6f1 100644
--- a/crates/persisting-agentctl/Cargo.toml
+++ b/crates/persisting-agentctl/Cargo.toml
@@ -8,13 +8,9 @@ description = "Agent control contracts, policies, wire protocol, and client SDK"
[dependencies]
anyhow.workspace = true
-libc.workspace = true
ipnet.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
-tempfile.workspace = true
-tokio = { workspace = true, features = ["fs", "io-util", "macros", "process", "rt", "time"] }
-tokio-util = { workspace = true, features = ["rt"] }
[dev-dependencies]
proptest.workspace = true
diff --git a/crates/persisting-agentctl/README.md b/crates/persisting-agentctl/README.md
index 5390f02d5..26eb1e6c2 100644
--- a/crates/persisting-agentctl/README.md
+++ b/crates/persisting-agentctl/README.md
@@ -4,11 +4,11 @@
synchronous client SDK.**
Owns the runtime control state machine, the wire protocol, and
-`AgentCtlClient`. AgentCtl is an optional, cooperative channel between pVisor
+`AgentCtlClient`. AgentCtl is an optional, cooperative channel between a control server
and a Run-local runtime client. It is not a sandbox, does not discover
processes or external effects, and is never enforcement evidence by itself.
-pVisor owns the Run-scoped server and credential injection. OverlayNet and
+Integrations provide the Run-scoped server and credential injection. OverlayNet and
Gateway apply policy decisions; they do not own this protocol.
```text
@@ -19,7 +19,7 @@ Requested -> Allowed / Denied -> Applied / Failed
- `ControlController` evaluates policy and returns the authorization transition.
- `ControlMachine` validates transitions and retains the state/history.
- `protocol` is the dependency-light request/response schema shared with
- pVisor's server.
+ the control server.
- `AgentCtlClient` discovers the authenticated Unix endpoint from the
environment and drives Session creation, periodic state synchronization, and
checkpoint quiescence.
@@ -28,13 +28,13 @@ An `Applied { effect: Deny }` state means the driver successfully blocked an
operation. It does not mean that a proxy-based driver is non-bypassable.
The protocol has two requests: `Hello` authenticates and opens a Session;
-`Sync` exchanges the client's current state for pVisor's current directive.
-Clients report `active`, `idle`, or `quiesced { checkpoint_id }`. pVisor replies
+`Sync` exchanges the client's current state for the control server's current directive.
+Clients report `active`, `idle`, or `quiesced { checkpoint_id }`. The server replies
with `continue`, `quiesce { checkpoint_id, deadline_unix_ms? }`, or
`shutdown { reason? }`. A checkpoint succeeds only after every Session frozen
into that checkpoint reports the matching quiesced state.
-pVisor injects four Run-local variables: `PERSISTING_AGENTCTL_ENDPOINT`,
+Integrations provide four Run-local variables: `PERSISTING_AGENTCTL_ENDPOINT`,
`PERSISTING_AGENTCTL_TOKEN`, `PERSISTING_AGENTCTL_VERSION` (exactly `1`), and
`PERSISTING_AGENTCTL_TRANSPORT` (currently `unix`). New integrations use only
`PERSISTING_AGENTCTL_*`. A future interactive login or terminal will use a
@@ -50,7 +50,7 @@ examples, state semantics, typed errors, and safety boundary.
use persisting_agentctl::{AgentCtlClient, AgentCtlClientConfig, AgentState};
let Some(config) = AgentCtlClientConfig::from_current_environment("worker-1")? else {
- return Ok(()); // not running under pVisor
+ return Ok(()); // no control endpoint configured
};
let mut client = AgentCtlClient::new(config);
let directive = client.connect()?;
@@ -65,7 +65,4 @@ just test persisting-agentctl
## Links
-- [pVisor isolation architecture](../../docs/src/pvisor/design/isolation.md)
-- [OverlayNet architecture](../../docs/src/pvisor/design/overlaynet.md)
-- [System architecture](../../docs/src/system-design/architecture.md)
-- [`persisting-pvisor`](../persisting-pvisor/README.md)
+- [System architecture](../../docs/src/en/system-design/architecture.md)
diff --git a/crates/persisting-agentctl/src/client.rs b/crates/persisting-agentctl/src/client.rs
index 0f63729ad..e674e1289 100644
--- a/crates/persisting-agentctl/src/client.rs
+++ b/crates/persisting-agentctl/src/client.rs
@@ -1,4 +1,4 @@
-//! Synchronous client for pVisor's cooperative, low-frequency AgentCtl protocol.
+//! Synchronous client for the cooperative, low-frequency AgentCtl protocol.
use crate::{
AGENTCTL_ENDPOINT_ENV, AGENTCTL_MAX_FRAME_BYTES, AGENTCTL_TOKEN_ENV, AGENTCTL_TRANSPORT_ENV,
@@ -143,7 +143,7 @@ impl AgentCtlClient {
}
}
- /// Report cooperative state and return pVisor's current directive.
+ /// Report cooperative state and return the server's current directive.
pub fn sync(&mut self, state: AgentState) -> anyhow::Result {
let session_id = self
.session_id
diff --git a/crates/persisting-agentctl/src/lib.rs b/crates/persisting-agentctl/src/lib.rs
index 900b6fedb..6b560013f 100644
--- a/crates/persisting-agentctl/src/lib.rs
+++ b/crates/persisting-agentctl/src/lib.rs
@@ -3,21 +3,16 @@
//! Drivers such as OverlayNet and Capture submit typed resources to a
//! [`ControlController`]. Authorization is represented as a state transition;
//! the driver then records whether the authorized operation was applied or
-//! failed. [`AgentCtlClient`] implements pVisor's optional cooperative
-//! AgentCtl protocol, while
-//! [`PVisorProcessClient`] submits a [`RunSpec`] to a standalone foreground
-//! pVisor binary. Supervisor messages are shared wire contracts rather than
-//! types owned by either pPilot or pVisor.
+//! failed. [`AgentCtlClient`] implements the optional cooperative AgentCtl
+//! protocol. Supervisor messages are shared wire contracts for integrations.
mod client;
-mod process;
pub mod protocol;
mod runtime;
mod supervisor;
pub use client::{AgentCtlClient, AgentCtlClientConfig, AgentCtlResponseError};
use ipnet::IpNet;
-pub use process::{PVisorProcessClient, PVisorProcessOptions};
pub use protocol::*;
pub use runtime::*;
pub use runtime::{AccessEffect as ControlEffect, AccessReason as ControlReason};
diff --git a/crates/persisting-agentctl/src/process.rs b/crates/persisting-agentctl/src/process.rs
deleted file mode 100644
index e8e0f7b86..000000000
--- a/crates/persisting-agentctl/src/process.rs
+++ /dev/null
@@ -1,192 +0,0 @@
-//! Foreground pVisor binary client used by control-plane components.
-
-use crate::{RunResult, RunSpec};
-use anyhow::{Context, Result};
-use serde::Deserialize;
-use std::ffi::OsString;
-use std::path::{Path, PathBuf};
-use std::process::Stdio;
-use std::time::Duration;
-use tokio::io::AsyncReadExt;
-use tokio::process::{Child, Command};
-use tokio_util::sync::CancellationToken;
-
-#[derive(Debug, Clone)]
-pub struct PVisorProcessClient {
- binary: PathBuf,
-}
-
-#[derive(Debug, Clone, Default)]
-pub struct PVisorProcessOptions {
- /// Root under which pVisor stores `/`, passed as a per-Run `--stage`.
- pub run_home: Option,
- /// Additional `pvisor run` switches, before `--spec`.
- pub run_args: Vec,
-}
-
-#[derive(Deserialize)]
-struct DelegatedRunOutput {
- result: RunResult,
-}
-
-impl PVisorProcessClient {
- pub fn new(binary: impl Into) -> Self {
- let binary = binary.into();
- let binary = if binary.components().count() == 1 {
- std::env::current_exe()
- .ok()
- .and_then(|current| {
- let parent = current.parent()?;
- let sibling = parent.join(&binary);
- if sibling.is_file() {
- return Some(sibling);
- }
- parent
- .parent()
- .map(|target_profile| target_profile.join(&binary))
- .filter(|candidate| candidate.is_file())
- })
- .unwrap_or(binary)
- } else {
- binary
- };
- Self { binary }
- }
-
- pub fn binary(&self) -> &Path {
- &self.binary
- }
-
- pub async fn run(
- &self,
- spec: &RunSpec,
- options: &PVisorProcessOptions,
- cancellation: CancellationToken,
- ) -> Result {
- let control = tempfile::Builder::new()
- .prefix("persisting-pvisor-client-")
- .tempdir()
- .context("create pVisor control directory")?;
- let spec_path = control.path().join("run-spec.json");
- let result_path = control.path().join("run-result.json");
- write_private_json(&spec_path, spec).await?;
-
- let mut command = self.command(spec, options, &spec_path, &result_path)?;
- let mut child = command
- .spawn()
- .with_context(|| format!("spawn pVisor binary {}", self.binary.display()))?;
- let stdout = child.stdout.take();
- let stderr = child.stderr.take();
- let stdout_task = tokio::spawn(read_pipe(stdout));
- let stderr_task = tokio::spawn(read_pipe(stderr));
-
- let status = tokio::select! {
- status = child.wait() => status.context("wait for pVisor process")?,
- _ = cancellation.cancelled() => terminate(&mut child).await?,
- };
- let stdout = stdout_task.await.context("join pVisor stdout reader")??;
- let stderr = stderr_task.await.context("join pVisor stderr reader")??;
-
- let output = match tokio::fs::read(&result_path).await {
- Ok(bytes) => serde_json::from_slice::(&bytes)
- .context("decode pVisor RunResult")?,
- Err(error) => {
- anyhow::bail!(
- "pVisor exited with {status} without a RunResult ({error}); stdout: {}; stderr: {}",
- String::from_utf8_lossy(&stdout).trim(),
- String::from_utf8_lossy(&stderr).trim(),
- )
- }
- };
- Ok(output.result)
- }
-
- fn command(
- &self,
- spec: &RunSpec,
- options: &PVisorProcessOptions,
- spec_path: &Path,
- result_path: &Path,
- ) -> Result {
- let mut command = Command::new(&self.binary);
- command.arg("run").args(&options.run_args);
- command
- .arg("--spec")
- .arg(spec_path)
- .arg("--result-file")
- .arg(result_path)
- .stdin(Stdio::null())
- .stdout(Stdio::piped())
- .stderr(Stdio::piped())
- .kill_on_drop(true);
- if let Some(run_home) = &options.run_home {
- // RunId is an opaque protocol identifier, not a trusted path.
- // Preserve the historical / layout without
- // allowing an ID to escape the caller's storage root.
- let run_id = spec.run_id.as_str();
- anyhow::ensure!(
- !run_id.is_empty()
- && run_id != "."
- && run_id != ".."
- && !run_id.contains(['/', '\\', '\0']),
- "Run ID must be a single directory name for pVisor stage storage"
- );
- // Absolute paths cannot be confused with --stage drop/drop:...
- // and remain independent of the Agent's cwd in the JSON spec.
- let run_home = if run_home.is_absolute() {
- run_home.clone()
- } else {
- std::env::current_dir()
- .context("resolve pVisor Run storage root")?
- .join(run_home)
- };
- command.arg("--stage").arg(run_home.join(run_id));
- }
- Ok(command)
- }
-}
-
-async fn read_pipe(pipe: Option) -> std::io::Result>
-where
- R: tokio::io::AsyncRead + Unpin,
-{
- let mut bytes = Vec::new();
- if let Some(mut pipe) = pipe {
- pipe.read_to_end(&mut bytes).await?;
- }
- Ok(bytes)
-}
-
-async fn terminate(child: &mut Child) -> Result {
- #[cfg(unix)]
- if let Some(pid) = child.id() {
- // SAFETY: `pid` belongs to the live child and SIGTERM is the delegated
- // pVisor shutdown contract, allowing it to publish a cancelled result.
- let sent = unsafe { libc::kill(pid as libc::pid_t, libc::SIGTERM) };
- if sent != 0 {
- child.start_kill().context("kill pVisor process")?;
- }
- }
- #[cfg(not(unix))]
- child.start_kill().context("kill pVisor process")?;
-
- match tokio::time::timeout(Duration::from_secs(5), child.wait()).await {
- Ok(status) => status.context("wait for terminating pVisor process"),
- Err(_) => {
- child.start_kill().context("force-kill pVisor process")?;
- child.wait().await.context("wait for killed pVisor process")
- }
- }
-}
-
-async fn write_private_json(path: &Path, value: &impl serde::Serialize) -> Result<()> {
- tokio::fs::write(path, serde_json::to_vec_pretty(value)?)
- .await
- .with_context(|| format!("write {}", path.display()))?;
- #[cfg(unix)]
- {
- use std::os::unix::fs::PermissionsExt;
- tokio::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600)).await?;
- }
- Ok(())
-}
diff --git a/crates/persisting-agentctl/src/protocol.rs b/crates/persisting-agentctl/src/protocol.rs
index 7dbb0a59b..01605b95b 100644
--- a/crates/persisting-agentctl/src/protocol.rs
+++ b/crates/persisting-agentctl/src/protocol.rs
@@ -1,8 +1,8 @@
//! AgentCtl v1 Control-plane wire contract.
//!
-//! AgentCtl is an optional, cooperative channel between pVisor and runtime
+//! AgentCtl is an optional, cooperative channel between a control server and runtime
//! clients inside one Run. A client authenticates once with [`AgentRequest::Hello`]
-//! and then periodically exchanges its [`AgentState`] for pVisor's current
+//! and then periodically exchanges its [`AgentState`] for the server's current
//! [`AgentDirective`] through [`AgentRequest::Sync`]. Each bounded,
//! newline-delimited JSON connection carries exactly one request and one
//! response.
@@ -15,7 +15,7 @@
//! client must observe before admitting work. A successful `Sync` refreshes
//! Session liveness and returns the latest directive.
//!
-//! Outside a checkpoint, pVisor considers a Session stale after it misses
+//! Outside a checkpoint, the server considers a Session stale after it misses
//! three recommended Sync intervals. A later `Hello` may replace a stale
//! Session. Live duplicates and capacity conflicts are rejected. Staleness is
//! diagnostic and lifecycle state; it is not proof that the client process
@@ -23,17 +23,17 @@
//!
//! # Checkpoint protocol
//!
-//! When pVisor publishes [`AgentDirective::Quiesce`], every runtime Session
+//! When the server publishes [`AgentDirective::Quiesce`], every runtime Session
//! that was live at checkpoint start must stop accepting work, drain in-flight
//! work, and report [`AgentState::Quiesced`] with the same checkpoint ID.
-//! pVisor freezes that participant set, rejects every new or replacement
+//! The server freezes that participant set, rejects every new or replacement
//! `Hello`, and never expires a participant until the checkpoint completes or
//! is abandoned. Each checkpoint attempt requires a fresh matching report;
//! an acknowledgement retained from an earlier attempt cannot satisfy it.
//!
//! A new `Quiesced` report whose ID does not match the active checkpoint is a
//! conflict. Repeating the exact accepted report is idempotent, including
-//! after pVisor publishes `Continue` or `Shutdown`, so a still-quiesced client
+//! after the server publishes `Continue` or `Shutdown`, so a still-quiesced client
//! can learn that new directive. Reporting `Active` or `Idle` while draining
//! removes any current acknowledgement. Clients remain quiesced until they
//! observe [`AgentDirective::Continue`] or [`AgentDirective::Shutdown`]. A
@@ -53,7 +53,7 @@
//!
//! Client states are Agent declarations. They are not enforcement evidence,
//! an authoritative process inventory, or proof that unreported external
-//! effects do not exist. pVisor obtains authoritative process facts from its
+//! effects do not exist. Integrations obtain authoritative process facts from the
//! execution provider.
//!
//! # Wire examples
@@ -96,7 +96,7 @@ pub const AGENTCTL_VERSION_ENV: &str = "PERSISTING_AGENTCTL_VERSION";
/// Environment variable naming the transport, currently `unix`.
pub const AGENTCTL_TRANSPORT_ENV: &str = "PERSISTING_AGENTCTL_TRANSPORT";
-/// A request sent by a runtime client to pVisor.
+/// A request sent by a runtime client to the server.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AgentRequest {
@@ -104,12 +104,12 @@ pub enum AgentRequest {
Hello {
/// Protocol version spoken by the client.
version: u32,
- /// Run-scoped bearer token injected by pVisor.
+ /// Run-scoped bearer token injected by the server.
token: String,
/// Non-empty client identity, unique among live Sessions in the Run.
client_id: String,
},
- /// Report current cooperative state and obtain pVisor's current directive.
+ /// Report current cooperative state and obtain the server's current directive.
Sync {
/// Protocol version spoken by the client.
version: u32,
@@ -135,7 +135,7 @@ pub enum AgentState {
},
}
-/// Desired cooperative state published by pVisor.
+/// Desired cooperative state published by the server.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum AgentDirective {
@@ -171,7 +171,7 @@ pub enum AgentErrorCode {
Conflict,
}
-/// A response sent by pVisor to a runtime client.
+/// A response sent by the server to a runtime client.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AgentResponse {
diff --git a/crates/persisting-agentctl/src/runtime.rs b/crates/persisting-agentctl/src/runtime.rs
index f338fbb62..2cd76a570 100644
--- a/crates/persisting-agentctl/src/runtime.rs
+++ b/crates/persisting-agentctl/src/runtime.rs
@@ -1,4 +1,4 @@
-//! Stable value types shared by pVisor, pPilot, capture, and storage.
+//! Stable value types shared by capture, storage, and runtime integrations.
//!
//! The runtime and narrative dimensions are deliberately orthogonal:
//!
@@ -64,7 +64,7 @@ string_id!(RunId);
string_id!(AttemptId);
string_id!(StorylineId);
-/// Connection material injected by pPilot into a RunSpec it launches.
+/// Connection material supplied by a coordinator in a RunSpec.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SupervisorBootstrap {
pub endpoint: String,
@@ -72,8 +72,8 @@ pub struct SupervisorBootstrap {
pub controller_epoch: u64,
#[serde(default = "default_supervisor_connect_timeout_ms")]
pub connect_timeout_ms: u64,
- /// pChronicle root used by pVisor to publish durable Attempt liveness and
- /// terminal results. This is optional for standalone pVisor Runs.
+ /// pChronicle root used to publish durable Attempt liveness and
+ /// terminal results. This is optional for standalone Runs.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub attempt_registry_uri: Option,
#[serde(default = "default_attempt_ttl_ms")]
@@ -100,13 +100,13 @@ impl AgentRef {
}
}
-/// One semantic Agent execution submitted to pVisor.
+/// One semantic Agent execution described by a runtime integration.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RunSpec {
#[serde(default = "runtime_schema_version")]
pub schema_version: u32,
pub run_id: RunId,
- /// Monotonic pPilot ownership generation. Zero is reserved for callers that
+ /// Monotonic coordinator ownership generation. Zero is reserved for callers that
/// do not use durable orchestration/fencing.
#[serde(default)]
pub lease_epoch: u64,
@@ -122,8 +122,8 @@ pub struct RunSpec {
pub runtime: RuntimeConfig,
#[serde(default)]
pub capabilities: CapabilitySet,
- /// Optional pPilot control channel. Absence, connection failure, or later
- /// disconnection never prevents standalone pVisor execution.
+ /// Optional coordinator control channel. Absence, connection failure, or later
+ /// disconnection never prevents standalone execution.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub supervisor: Option,
#[serde(default)]
@@ -213,7 +213,7 @@ pub enum StdioMode {
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RuntimeConfig {
- /// Wall-clock limit for one Attempt. `None` means no pVisor deadline.
+ /// Wall-clock limit for one Attempt. `None` means no execution deadline.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub timeout_ms: Option,
/// Grace period between a cooperative process-tree termination request and
@@ -382,7 +382,7 @@ pub struct NetworkBandwidthLimit {
pub bytes_per_second: u64,
}
-/// Runtime-neutral network request presented to pVisor for authorization.
+/// Runtime-neutral network request presented to a controller for authorization.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkAccessRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -410,7 +410,7 @@ pub enum NetworkTransport {
}
/// Model invocation metadata. Request/response bodies intentionally stay in
-/// Capture; pVisor receives only the information needed for policy and audit.
+/// Capture; the controller receives only the information needed for policy and audit.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelCallRequest {
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -794,7 +794,7 @@ pub struct RunResult {
pub warnings: Vec,
}
-/// The current pPilot execution owner for one logical Run.
+/// The current execution owner for one logical Run.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RunLeaseRecord {
pub run_id: RunId,
@@ -809,7 +809,7 @@ pub struct RunLeaseRecord {
}
/// Immutable terminal commit request. `result_digest` binds the commit to the
-/// durable pPilot completion record without embedding an arbitrarily large
+/// durable completion record without embedding an arbitrarily large
/// result in the control object.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RunCommitRequest {
diff --git a/crates/persisting-agentctl/src/supervisor.rs b/crates/persisting-agentctl/src/supervisor.rs
index 43df056c0..be081daf8 100644
--- a/crates/persisting-agentctl/src/supervisor.rs
+++ b/crates/persisting-agentctl/src/supervisor.rs
@@ -1,4 +1,4 @@
-//! Versioned pPilot Supervisor protocol shared by the control and execution planes.
+//! Versioned Supervisor protocol shared by the control and execution planes.
use crate::{AttemptId, NetworkBandwidthLimit, RunId};
use serde::{Deserialize, Serialize};
@@ -38,7 +38,7 @@ pub enum SupervisorClientMessage {
Ack(SupervisorDirectiveAck),
}
-/// A time-bounded rate grant enforced locally by pVisor.
+/// A time-bounded rate grant enforced locally by a runtime integration.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SupervisorNetworkQuotaGrant {
pub grant_id: String,
diff --git a/crates/persisting-dlcapt/src/capture/step_table_writer.rs b/crates/persisting-dlcapt/src/capture/step_table_writer.rs
index 3d1caf995..09aab7cb7 100644
--- a/crates/persisting-dlcapt/src/capture/step_table_writer.rs
+++ b/crates/persisting-dlcapt/src/capture/step_table_writer.rs
@@ -8,6 +8,10 @@ pub use crate::capture::writers::lance_crate::LanceCrateWriter;
/// 单表 session_steps 追加写;一步一行。
#[async_trait]
+#[allow(
+ clippy::double_must_use,
+ reason = "async_trait adds must_use to futures"
+)]
pub trait StepTableWriter: Send + Sync {
async fn append(&self, record: &StepRecord) -> Result<()>;
}
diff --git a/crates/persisting-events/src/control.rs b/crates/persisting-events/src/control.rs
index 46760157d..68e210810 100644
--- a/crates/persisting-events/src/control.rs
+++ b/crates/persisting-events/src/control.rs
@@ -1,7 +1,7 @@
//! Lightweight, versioned control-plane client for the standalone pChronicle process.
//!
//! This optional module contains no storage engine. pChronicle implements the
-//! durable operations; orchestrators such as pPilot depend only on these
+//! durable operations; local integrations depend only on these
//! contracts and the long-lived process transport.
use crate::{EventRecord, unix_now_ms};
@@ -222,6 +222,10 @@ pub enum ChronicleControlResponse {
}
#[async_trait]
+#[allow(
+ clippy::double_must_use,
+ reason = "async_trait adds must_use to futures"
+)]
pub trait ChronicleControl: Send + Sync {
fn root_uri(&self) -> &str;
diff --git a/crates/persisting-events/src/lib.rs b/crates/persisting-events/src/lib.rs
index 6d43fc6c5..4fb40eb5c 100644
--- a/crates/persisting-events/src/lib.rs
+++ b/crates/persisting-events/src/lib.rs
@@ -1,6 +1,6 @@
//! Storage-independent runtime event contracts shared by Persisting components.
//!
-//! Producers such as pVisor and Gateway emit these records. Consumers such as
+//! Producers such as Gateway emit these records. Consumers such as
//! pChronicle decide how to persist, query, and project them.
//! The optional `control` feature also carries the lightweight, versioned
//! sidecar protocol so callers do not need another protocol-only package.
diff --git a/crates/persisting-gateway/README.md b/crates/persisting-gateway/README.md
index dae41e7c9..791846837 100644
--- a/crates/persisting-gateway/README.md
+++ b/crates/persisting-gateway/README.md
@@ -1,6 +1,6 @@
# persisting-gateway
-**pVisor's built-in Agent protocol driver: LLM HTTP forwarding plus canonical
+**pChronicle Agent protocol gateway: LLM HTTP forwarding and canonical
trajectory capture.**
Owns the application-level path from Agent/LLM HTTP exchanges to trajectory
@@ -14,9 +14,8 @@ transport, access enforcement, and generic sink dispatch.
[`persisting-pchronicle`](../persisting-pchronicle/README.md) owns schemas,
persistence, reading, replay, conversion, and derived views.
-Capture remains the user-facing capability. It runs through `pvisor run` or
-`pchronicle serve --gateway-config`. Gateway is an internal pVisor driver and a
-reusable crate, not a peer product or standalone service.
+Capture runs through `pchronicle serve --gateway-config`. Gateway is an internal
+library; external execution components can also integrate it.
This crate implements `persisting-overlaynet::OverlaySink`. Protocol rendering
and capture share one in-memory `LlmRequestEventPayload` (`llm/v1`). Provider
@@ -38,8 +37,6 @@ benchmarks and regressions. It does not start Gateway itself.
## Links
-- [Gateway architecture](../../docs/src/pvisor/design/gateway.md)
-- [Capture trajectories](../../docs/src/pvisor/guides/capture.md)
-- [Gateway forwarding, rewriting, and capture](../../docs/src/pchronicle/guides/serve-gateway.md)
+- [Gateway forwarding, rewriting, and capture](../../docs/src/en/pchronicle/guides/serve-gateway.md)
- [`persisting-overlaynet`](../persisting-overlaynet/README.md)
- [`persisting-pchronicle`](../persisting-pchronicle/README.md)
diff --git a/crates/persisting-gateway/src/config.rs b/crates/persisting-gateway/src/config.rs
index 053dac85c..e4ab20377 100644
--- a/crates/persisting-gateway/src/config.rs
+++ b/crates/persisting-gateway/src/config.rs
@@ -31,13 +31,13 @@ pub struct ProxyConfig {
/// Harbor-aligned egress policy for forward-proxy traffic (`CONNECT` + absolute-URI).
#[serde(default)]
pub network: NetworkConfig,
- /// Optional embedded OverlayFS mount for the Attempt (consumed by pVisor).
+ /// Optional overlay configuration for integrations; Gateway does not mount it.
#[serde(default)]
pub overlay: OverlayConfig,
pub models: Vec,
}
-/// Filesystem overlay settings (same capture TOML; applied by pVisor).
+/// Filesystem overlay settings retained in capture TOML for integrations.
///
/// Model: **target** (read-only base / apply destination) + **staging** (upper
/// holds deltas). The Agent sees `merged`; changes do **not** touch `target`
@@ -45,7 +45,7 @@ pub struct ProxyConfig {
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct OverlayConfig {
- /// When true, pVisor mounts its embedded OverlayFS for the Attempt.
+ /// Whether the integration requests an overlay for the Attempt.
#[serde(default)]
pub enabled: bool,
/// Target filesystem: primary lower layer and destination for `apply`.
@@ -70,7 +70,7 @@ pub struct OverlayConfig {
/// operation log (overrides `{storage}/.overlay/jujutsu`).
#[serde(default)]
pub jujutsu_store_path: Option,
- /// Jujutsu workspace/fork name (defaults to the pVisor session id).
+ /// Jujutsu workspace/fork name for the integration.
#[serde(default)]
pub jujutsu_workspace: Option,
/// Writable upper directory (overrides `{stage_dir}/upper` when set).
@@ -83,7 +83,7 @@ pub struct OverlayConfig {
#[serde(default)]
pub merged_dir: Option,
/// If true, apply staging onto `target` automatically when the Attempt ends.
- /// Default false — review then `pvisor apply` or `pvisor drop`.
+ /// Default false; effects require an explicit apply or discard decision.
#[serde(default)]
pub auto_apply: bool,
/// If true, discard staging automatically when the Attempt ends.
diff --git a/crates/persisting-gateway/src/gateway/state.rs b/crates/persisting-gateway/src/gateway/state.rs
index 7e36d78b8..b54740e3b 100644
--- a/crates/persisting-gateway/src/gateway/state.rs
+++ b/crates/persisting-gateway/src/gateway/state.rs
@@ -136,7 +136,7 @@ pub async fn serve_with_listeners_and_shutdown(
/// Gateway sink with an injected runtime control state controller.
///
-/// pVisor injects the controller; Gateway and OverlayNet apply model/network
+/// The caller supplies the controller; Gateway and OverlayNet apply model/network
/// transitions while retaining HTTP adaptation and trajectory extraction.
pub async fn serve_with_runtime_control(
config: ProxyConfig,
diff --git a/crates/persisting-gateway/src/runtime/in_process.rs b/crates/persisting-gateway/src/runtime/in_process.rs
index 696063ad9..6b419516a 100644
--- a/crates/persisting-gateway/src/runtime/in_process.rs
+++ b/crates/persisting-gateway/src/runtime/in_process.rs
@@ -1,4 +1,4 @@
-//! In-process Gateway for one pVisor Attempt (no forked daemon).
+//! In-process Gateway for one Attempt (no forked daemon).
use std::net::TcpStream;
use std::path::PathBuf;
@@ -22,14 +22,14 @@ pub struct InProcessCapture {
}
/// Attempt-scoped network services shared by Gateway and other interception
-/// drivers owned by pVisor.
+/// drivers supplied by the caller.
#[derive(Clone)]
pub struct InProcessRuntime {
pub controller: Arc,
pub interception_metrics: InterceptionMetrics,
pub bandwidth_registry: BandwidthRegistry,
pub attempt_id: Option,
- /// Disable LLM dispatch for pVisor runs that only need the network proxy.
+ /// Disable LLM dispatch for Runs that only need the network proxy.
pub gateway_enabled: bool,
}
diff --git a/crates/persisting-gateway/src/runtime/run_config.rs b/crates/persisting-gateway/src/runtime/run_config.rs
index f8b11c6b8..5f30e7d1d 100644
--- a/crates/persisting-gateway/src/runtime/run_config.rs
+++ b/crates/persisting-gateway/src/runtime/run_config.rs
@@ -33,7 +33,7 @@ pub fn snapshot_run_proxy_config(
}
/// Write an already-resolved proxy configuration into the session snapshot.
-/// pVisor uses this after merging its TOML and CLI configuration, so Gateway
+/// Callers use this after resolving their configuration, so Gateway
/// runtime setup never needs to know where configuration originated.
pub fn snapshot_proxy_config(
storage: &Path,
diff --git a/crates/persisting-gateway/tests/README.md b/crates/persisting-gateway/tests/README.md
index cc4247bf5..f22d0f272 100644
--- a/crates/persisting-gateway/tests/README.md
+++ b/crates/persisting-gateway/tests/README.md
@@ -29,4 +29,4 @@ cargo nextest run -p persisting-gateway --test model_api_forwarding --locked
## Links
- [`persisting-gateway`](../README.md)
-- [Capture trajectories](../../../docs/src/pvisor/guides/capture.md)
+- [Capture trajectories](../../../docs/src/en/pchronicle/guides/serve-gateway.md)
diff --git a/crates/persisting-overlay-core/Cargo.toml b/crates/persisting-overlay-core/Cargo.toml
deleted file mode 100644
index 52cca78ed..000000000
--- a/crates/persisting-overlay-core/Cargo.toml
+++ /dev/null
@@ -1,17 +0,0 @@
-[package]
-name = "persisting-overlay-core"
-version.workspace = true
-edition.workspace = true
-authors.workspace = true
-license.workspace = true
-description = "FUSE-neutral portable overlay filesystem core for pVisor"
-
-[dependencies]
-libc.workspace = true
-log.workspace = true
-serde = { workspace = true, features = ["derive"] }
-serde_json.workspace = true
-sha2.workspace = true
-
-[dev-dependencies]
-tempfile.workspace = true
diff --git a/crates/persisting-overlay-core/src/core.rs b/crates/persisting-overlay-core/src/core.rs
deleted file mode 100644
index 988cd3798..000000000
--- a/crates/persisting-overlay-core/src/core.rs
+++ /dev/null
@@ -1,1410 +0,0 @@
-use crate::sys;
-use serde::{Deserialize, Serialize};
-use sha2::{Digest, Sha256};
-use std::collections::{BTreeSet, HashMap};
-use std::ffi::{OsStr, OsString};
-use std::fs::{self, File, Metadata, OpenOptions};
-use std::io::{self, Write};
-use std::os::unix::ffi::{OsStrExt, OsStringExt};
-use std::os::unix::fs::{MetadataExt, OpenOptionsExt, PermissionsExt};
-use std::path::{Component, Path, PathBuf};
-use std::sync::Mutex;
-use std::sync::atomic::{AtomicU64, Ordering};
-use std::time::{Duration, UNIX_EPOCH};
-
-pub const WHITEOUT_PREFIX: &str = ".wh.";
-pub const OPAQUE_NAME: &str = ".wh..wh..opq";
-const TEMP_PREFIX: &str = ".wh..persisting-copyup-";
-const PREIMAGE_COMPLETE_MARKER: &str = "complete-v1";
-const OPAQUE_XATTRS: [&str; 3] = [
- "trusted.overlay.opaque",
- "user.overlay.opaque",
- "user.fuseoverlayfs.opaque",
-];
-
-static TEMP_ID: AtomicU64 = AtomicU64::new(1);
-
-#[derive(Clone, Debug)]
-pub struct Resolved {
- pub path: PathBuf,
- pub is_upper: bool,
-}
-
-#[derive(Debug)]
-pub struct OverlayCore {
- lowers: Vec,
- upper: PathBuf,
- work: Option,
- excluded: BTreeSet,
- copied_hard_links: Mutex>,
- preimage_dir: Option,
- preimage_lock: Mutex<()>,
-}
-
-/// Durable first-touch state of one apply target path.
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-pub struct PathPreimage {
- /// Raw Unix path bytes relative to the overlay root.
- pub path: Vec,
- pub state: PathFingerprint,
-}
-
-impl PathPreimage {
- pub fn relative_path(&self) -> PathBuf {
- PathBuf::from(OsString::from_vec(self.path.clone()))
- }
-}
-
-/// Content and metadata relevant to detecting a destructive apply conflict.
-#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
-#[serde(tag = "kind", rename_all = "snake_case")]
-pub enum PathFingerprint {
- Absent,
- File {
- sha256: String,
- mode: u32,
- uid: u32,
- gid: u32,
- },
- Directory {
- mode: u32,
- uid: u32,
- gid: u32,
- mtime_seconds: i64,
- mtime_nanoseconds: i64,
- },
- Symlink {
- target: Vec,
- uid: u32,
- gid: u32,
- },
- Other {
- mode: u32,
- uid: u32,
- gid: u32,
- rdev: u64,
- },
-}
-
-fn error(errno: i32) -> io::Error {
- io::Error::from_raw_os_error(errno)
-}
-
-fn exists(path: &Path) -> bool {
- fs::symlink_metadata(path).is_ok()
-}
-
-fn ignorable_metadata_error(err: &io::Error) -> bool {
- matches!(
- err.raw_os_error(),
- Some(libc::EPERM) | Some(libc::EACCES) | Some(libc::ENOTSUP)
- )
-}
-
-fn ignorable_ownership_error(err: &io::Error) -> bool {
- // A uid or gid outside the current user namespace is reported as EINVAL.
- // Ownership is best-effort for an unprivileged overlay, just like EPERM.
- ignorable_metadata_error(err) || err.raw_os_error() == Some(libc::EINVAL)
-}
-
-fn sha256_hex(bytes: &[u8]) -> String {
- use std::fmt::Write as _;
-
- let digest = Sha256::digest(bytes);
- let mut encoded = String::with_capacity(digest.len() * 2);
- for byte in digest {
- let _ = write!(&mut encoded, "{byte:02x}");
- }
- encoded
-}
-
-/// Fingerprint one path without following its final symlink.
-pub fn fingerprint_at(root: &Path, rel: &Path) -> io::Result {
- OverlayCore::validate_rel(rel)?;
- let path = if rel.as_os_str().is_empty() {
- root.to_path_buf()
- } else {
- root.join(rel)
- };
- let metadata = match fs::symlink_metadata(&path) {
- Ok(metadata) => metadata,
- Err(error) if error.kind() == io::ErrorKind::NotFound => {
- return Ok(PathFingerprint::Absent);
- }
- Err(error) => return Err(error),
- };
- let kind = metadata.file_type();
- if kind.is_file() {
- return Ok(PathFingerprint::File {
- sha256: sha256_hex(&fs::read(&path)?),
- mode: metadata.mode(),
- uid: metadata.uid(),
- gid: metadata.gid(),
- });
- }
- if kind.is_dir() {
- return Ok(PathFingerprint::Directory {
- mode: metadata.mode(),
- uid: metadata.uid(),
- gid: metadata.gid(),
- mtime_seconds: metadata.mtime(),
- mtime_nanoseconds: metadata.mtime_nsec(),
- });
- }
- if kind.is_symlink() {
- return Ok(PathFingerprint::Symlink {
- target: fs::read_link(&path)?.into_os_string().into_vec(),
- uid: metadata.uid(),
- gid: metadata.gid(),
- });
- }
- Ok(PathFingerprint::Other {
- mode: metadata.mode(),
- uid: metadata.uid(),
- gid: metadata.gid(),
- rdev: metadata.rdev(),
- })
-}
-
-/// Load all durable first-touch entries from a preimage journal.
-pub fn load_preimages(directory: &Path) -> io::Result> {
- let entries = directory.join("entries");
- let iterator = match fs::read_dir(&entries) {
- Ok(iterator) => iterator,
- Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(Vec::new()),
- Err(error) => return Err(error),
- };
- let mut preimages = Vec::new();
- for entry in iterator {
- let entry = entry?;
- if entry.path().extension() != Some(OsStr::new("json")) {
- continue;
- }
- let preimage = serde_json::from_slice::(&fs::read(entry.path())?)
- .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
- OverlayCore::validate_rel(&preimage.relative_path())?;
- preimages.push(preimage);
- }
- preimages.sort_by(|left, right| left.path.cmp(&right.path));
- Ok(preimages)
-}
-
-pub fn preimage_journal_is_complete(directory: &Path) -> bool {
- directory.join(PREIMAGE_COMPLETE_MARKER).is_file()
-}
-
-/// Consume journal entries after their corresponding target paths commit.
-pub fn remove_preimages(directory: &Path, paths: &[PathBuf]) -> io::Result<()> {
- let entries = directory.join("entries");
- for path in paths {
- OverlayCore::validate_rel(path)?;
- let journal_path =
- entries.join(format!("{}.json", sha256_hex(path.as_os_str().as_bytes())));
- match fs::remove_file(journal_path) {
- Ok(()) => {}
- Err(error) if error.kind() == io::ErrorKind::NotFound => {}
- Err(error) => return Err(error),
- }
- }
- match File::open(entries) {
- Ok(directory) => directory.sync_all(),
- Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(()),
- Err(error) => Err(error),
- }
-}
-
-impl OverlayCore {
- pub fn new(lowers: Vec, upper: PathBuf, work: Option) -> io::Result {
- Self::new_with_exclusions(lowers, upper, work, Vec::new())
- }
-
- pub fn new_with_exclusions(
- lowers: Vec,
- upper: PathBuf,
- work: Option,
- excluded: Vec,
- ) -> io::Result {
- Self::new_with_exclusions_and_preimages(lowers, upper, work, excluded, None)
- }
-
- pub fn new_with_exclusions_and_preimages(
- lowers: Vec,
- upper: PathBuf,
- work: Option,
- excluded: Vec,
- preimage_dir: Option,
- ) -> io::Result {
- if lowers.is_empty() {
- return Err(error(libc::EINVAL));
- }
- for lower in &lowers {
- if !lower.is_dir() {
- return Err(error(libc::ENOTDIR));
- }
- }
- fs::create_dir_all(&upper)?;
- let upper_was_empty = fs::read_dir(&upper)?.next().is_none();
- if let Some(work) = &work {
- fs::create_dir_all(work)?;
- if work == &upper || work.starts_with(&upper) || upper.starts_with(work) {
- return Err(error(libc::EINVAL));
- }
- if fs::metadata(work)?.dev() != fs::metadata(&upper)?.dev() {
- return Err(error(libc::EXDEV));
- }
- for entry in fs::read_dir(work)? {
- let entry = entry?;
- if entry
- .file_name()
- .as_bytes()
- .starts_with(TEMP_PREFIX.as_bytes())
- {
- let path = entry.path();
- if fs::symlink_metadata(&path)?.is_dir() {
- fs::remove_dir_all(path)?;
- } else {
- fs::remove_file(path)?;
- }
- }
- }
- }
- let excluded = excluded
- .into_iter()
- .map(|path| {
- Self::validate_rel(&path)?;
- if path.as_os_str().is_empty() {
- return Err(error(libc::EINVAL));
- }
- Ok(path)
- })
- .collect::>>()?;
- if let Some(directory) = &preimage_dir {
- fs::create_dir_all(directory.join("entries"))?;
- if upper_was_empty && !preimage_journal_is_complete(directory) {
- let marker = directory.join(PREIMAGE_COMPLETE_MARKER);
- let mut file = OpenOptions::new()
- .write(true)
- .create_new(true)
- .mode(0o600)
- .open(marker)?;
- file.write_all(b"pvisor-overlay-preimage-journal-v1\n")?;
- file.sync_all()?;
- File::open(directory)?.sync_all()?;
- }
- }
- let core = Self {
- lowers,
- upper,
- work,
- excluded,
- copied_hard_links: Mutex::new(HashMap::new()),
- preimage_dir,
- preimage_lock: Mutex::new(()),
- };
- if fs::read_dir(&core.upper)?.next().is_none()
- && let Some(root) = core.lowers.first()
- {
- let metadata = fs::symlink_metadata(root)?;
- core.copy_metadata(root, &core.upper, &metadata)?;
- }
- Ok(core)
- }
-
- fn record_preimage(&self, rel: &Path) -> io::Result<()> {
- let Some(directory) = &self.preimage_dir else {
- return Ok(());
- };
- Self::validate_rel(rel)?;
- let _guard = self
- .preimage_lock
- .lock()
- .map_err(|_| io::Error::other("preimage journal lock poisoned"))?;
- let path_bytes = rel.as_os_str().as_bytes();
- let destination = directory
- .join("entries")
- .join(format!("{}.json", sha256_hex(path_bytes)));
- match fs::symlink_metadata(&destination) {
- Ok(_) => return Ok(()),
- Err(error) if error.kind() == io::ErrorKind::NotFound => {}
- Err(error) => return Err(error),
- }
- let target = self.lowers.last().ok_or_else(|| error(libc::EINVAL))?;
- let preimage = PathPreimage {
- path: path_bytes.to_vec(),
- state: fingerprint_at(target, rel)?,
- };
- let body = serde_json::to_vec_pretty(&preimage)
- .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?;
- let mut file = OpenOptions::new()
- .write(true)
- .create_new(true)
- .mode(0o600)
- .open(&destination)?;
- file.write_all(&body)?;
- file.sync_all()?;
- File::open(directory.join("entries"))?.sync_all()
- }
-
- fn record_logical_tree_mapping(&self, source: &Path, destination: &Path) -> io::Result<()> {
- self.record_preimage(destination)?;
- if !self.metadata(source)?.is_dir() {
- return Ok(());
- }
- for name in self.list_names(source)? {
- let source_child = Self::child(source, &name)?;
- let destination_child = Self::child(destination, &name)?;
- self.record_logical_tree_mapping(&source_child, &destination_child)?;
- }
- Ok(())
- }
-
- pub fn upper(&self) -> &Path {
- &self.upper
- }
-
- fn is_excluded(&self, rel: &Path) -> bool {
- self.excluded
- .iter()
- .any(|prefix| rel == prefix || rel.starts_with(prefix))
- }
-
- fn require_visible(&self, rel: &Path) -> io::Result<()> {
- Self::validate_rel(rel)?;
- if self.is_excluded(rel) {
- return Err(error(libc::ENOENT));
- }
- Ok(())
- }
-
- pub fn validate_rel(rel: &Path) -> io::Result<()> {
- if rel.is_absolute()
- || rel
- .components()
- .any(|component| !matches!(component, Component::Normal(_)))
- {
- return Err(error(libc::EINVAL));
- }
- Ok(())
- }
-
- pub fn validate_name(name: &OsStr) -> io::Result<()> {
- let bytes = name.as_bytes();
- if bytes.is_empty()
- || bytes == b"."
- || bytes == b".."
- || bytes.contains(&b'/')
- || bytes.starts_with(WHITEOUT_PREFIX.as_bytes())
- {
- return Err(error(libc::EINVAL));
- }
- Ok(())
- }
-
- pub fn child(parent: &Path, name: &OsStr) -> io::Result {
- Self::validate_rel(parent)?;
- Self::validate_name(name)?;
- Ok(if parent.as_os_str().is_empty() {
- PathBuf::from(name)
- } else {
- parent.join(name)
- })
- }
-
- pub fn upper_path(&self, rel: &Path) -> PathBuf {
- if rel.as_os_str().is_empty() {
- self.upper.clone()
- } else {
- self.upper.join(rel)
- }
- }
-
- fn whiteout_path(&self, parent: &Path, name: &OsStr) -> PathBuf {
- let mut marker = OsString::from(WHITEOUT_PREFIX);
- marker.push(name);
- self.upper_path(parent).join(marker)
- }
-
- pub fn is_whiteout_name(name: &OsStr) -> bool {
- name.as_bytes().starts_with(WHITEOUT_PREFIX.as_bytes())
- }
-
- fn is_whiteouted(&self, parent: &Path, name: &OsStr) -> bool {
- exists(&self.whiteout_path(parent, name))
- }
-
- pub fn is_opaque(&self, rel: &Path) -> bool {
- let path = self.upper_path(rel);
- exists(&path.join(OPAQUE_NAME))
- || OPAQUE_XATTRS.iter().any(|name| {
- sys::get_xattr(&path, OsStr::new(name)).is_ok_and(|value| value == b"y")
- })
- }
-
- fn resolve_component(&self, rel: &Path) -> Option {
- let upper = self.upper_path(rel);
- if exists(&upper) {
- return Some(Resolved {
- path: upper,
- is_upper: true,
- });
- }
- let name = rel.file_name()?;
- let parent = rel.parent().unwrap_or_else(|| Path::new(""));
- if self.is_whiteouted(parent, name) || self.is_opaque(parent) {
- return None;
- }
- self.lowers.iter().find_map(|lower| {
- let path = lower.join(rel);
- exists(&path).then_some(Resolved {
- path,
- is_upper: false,
- })
- })
- }
-
- pub fn resolve(&self, rel: &Path) -> Option {
- if self.require_visible(rel).is_err() {
- return None;
- }
- if rel.as_os_str().is_empty() {
- return Some(Resolved {
- path: self.upper.clone(),
- is_upper: true,
- });
- }
- let mut current = PathBuf::new();
- let mut resolved = None;
- let count = rel.components().count();
- for (index, component) in rel.components().enumerate() {
- current.push(component.as_os_str());
- let item = self.resolve_component(¤t)?;
- if index + 1 != count {
- let metadata = fs::symlink_metadata(&item.path).ok()?;
- if !metadata.is_dir() {
- return None;
- }
- }
- resolved = Some(item);
- }
- resolved
- }
-
- pub fn metadata(&self, rel: &Path) -> io::Result {
- self.require_visible(rel)?;
- let resolved = self.resolve(rel).ok_or_else(|| error(libc::ENOENT))?;
- fs::symlink_metadata(resolved.path)
- }
-
- pub fn exists_in_lower(&self, rel: &Path) -> bool {
- if self.require_visible(rel).is_err() {
- return false;
- }
- self.lowers.iter().any(|lower| exists(&lower.join(rel)))
- }
-
- fn copy_metadata(
- &self,
- source: &Path,
- destination: &Path,
- metadata: &Metadata,
- ) -> io::Result<()> {
- let nofollow = metadata.file_type().is_symlink();
- if let Err(err) = sys::chown(destination, metadata.uid(), metadata.gid(), nofollow)
- && !ignorable_ownership_error(&err)
- {
- return Err(err);
- }
- if !nofollow {
- fs::set_permissions(
- destination,
- fs::Permissions::from_mode(metadata.mode() & 0o7777),
- )?;
- }
- if let Err(err) = sys::copy_xattrs(source, destination)
- && !ignorable_metadata_error(&err)
- {
- return Err(err);
- }
- let atime = UNIX_EPOCH
- + Duration::new(metadata.atime().max(0) as u64, metadata.atime_nsec() as u32);
- let mtime = UNIX_EPOCH
- + Duration::new(metadata.mtime().max(0) as u64, metadata.mtime_nsec() as u32);
- if let Err(err) = sys::set_times(destination, Some(atime), Some(mtime), nofollow)
- && !ignorable_metadata_error(&err)
- {
- return Err(err);
- }
- Ok(())
- }
-
- pub fn ensure_upper_parents(&self, rel: &Path) -> io::Result<()> {
- self.require_visible(rel)?;
- Self::validate_rel(rel)?;
- let Some(parent) = rel.parent() else {
- return Ok(());
- };
- let mut current = PathBuf::new();
- for component in parent.components() {
- current.push(component.as_os_str());
- let upper = self.upper_path(¤t);
- if exists(&upper) {
- if !fs::symlink_metadata(&upper)?.is_dir() {
- return Err(error(libc::ENOTDIR));
- }
- continue;
- }
- let resolved = self.resolve(¤t).ok_or_else(|| error(libc::ENOENT))?;
- let metadata = fs::symlink_metadata(&resolved.path)?;
- if !metadata.is_dir() {
- return Err(error(libc::ENOTDIR));
- }
- // Creating a child changes this copied-up directory's metadata,
- // and apply may promote that metadata even though the Agent did
- // not issue an explicit setattr on the parent.
- self.record_preimage(¤t)?;
- fs::create_dir(&upper)?;
- self.copy_metadata(&resolved.path, &upper, &metadata)?;
- }
- Ok(())
- }
-
- fn temporary_path(&self, parent: &Path) -> PathBuf {
- let id = TEMP_ID.fetch_add(1, Ordering::Relaxed);
- self.work
- .as_deref()
- .unwrap_or(parent)
- .join(format!("{TEMP_PREFIX}{}-{id}", std::process::id()))
- }
-
- pub fn copy_up(&self, rel: &Path) -> io::Result {
- self.require_visible(rel)?;
- Self::validate_rel(rel)?;
- self.record_preimage(rel)?;
- let upper = self.upper_path(rel);
- if exists(&upper) {
- return Ok(upper);
- }
- let resolved = self.resolve(rel).ok_or_else(|| error(libc::ENOENT))?;
- if resolved.is_upper {
- return Ok(resolved.path);
- }
- self.ensure_upper_parents(rel)?;
- let metadata = fs::symlink_metadata(&resolved.path)?;
- let parent = upper.parent().ok_or_else(|| error(libc::EINVAL))?;
- let temporary = self.temporary_path(parent);
- let result = (|| {
- let kind = metadata.file_type();
- if kind.is_dir() {
- fs::create_dir(&temporary)?;
- } else if kind.is_symlink() {
- std::os::unix::fs::symlink(fs::read_link(&resolved.path)?, &temporary)?;
- } else if kind.is_file() {
- let identity = (metadata.dev(), metadata.ino());
- let existing = if metadata.nlink() > 1 {
- self.copied_hard_links
- .lock()
- .ok()
- .and_then(|links| links.get(&identity).cloned())
- .filter(|path| exists(path))
- } else {
- None
- };
- if let Some(existing) = existing {
- fs::hard_link(existing, &temporary)?;
- } else {
- let mut options = OpenOptions::new();
- options
- .write(true)
- .create_new(true)
- .mode(metadata.mode() & 0o7777);
- let mut destination = options.open(&temporary)?;
- let mut source = File::open(&resolved.path)?;
- io::copy(&mut source, &mut destination)?;
- }
- } else {
- sys::mknod(&temporary, metadata.mode(), metadata.rdev() as u32)?;
- }
- self.copy_metadata(&resolved.path, &temporary, &metadata)?;
- fs::rename(&temporary, &upper)?;
- if metadata.is_file()
- && metadata.nlink() > 1
- && let Ok(mut links) = self.copied_hard_links.lock()
- {
- links.insert((metadata.dev(), metadata.ino()), upper.clone());
- }
- Ok(())
- })();
- if result.is_err() {
- let _ = if temporary.is_dir() {
- fs::remove_dir_all(&temporary)
- } else {
- fs::remove_file(&temporary)
- };
- }
- result.map(|()| upper)
- }
-
- pub fn list_names(&self, rel: &Path) -> io::Result> {
- self.require_visible(rel)?;
- let metadata = self.metadata(rel)?;
- if !metadata.is_dir() {
- return Err(error(libc::ENOTDIR));
- }
- let mut names = BTreeSet::new();
- if !self.is_opaque(rel) {
- for lower in &self.lowers {
- let directory = lower.join(rel);
- let Ok(entries) = fs::read_dir(directory) else {
- continue;
- };
- for entry in entries.flatten() {
- let name = entry.file_name();
- if !Self::is_whiteout_name(&name) {
- names.insert(name);
- }
- }
- }
- }
- if let Ok(entries) = fs::read_dir(self.upper_path(rel)) {
- for entry in entries.flatten() {
- let name = entry.file_name();
- if !Self::is_whiteout_name(&name) {
- names.insert(name);
- }
- }
- }
- names.retain(|name| {
- !self.is_whiteouted(rel, name)
- && Self::child(rel, name).is_ok_and(|child| !self.is_excluded(&child))
- });
- Ok(names.into_iter().collect())
- }
-
- fn mark_opaque(&self, rel: &Path) -> io::Result<()> {
- let path = self.upper_path(rel);
- let marker = path.join(OPAQUE_NAME);
- if !exists(&marker) {
- OpenOptions::new()
- .write(true)
- .create_new(true)
- .mode(0o600)
- .open(marker)?;
- }
- for name in OPAQUE_XATTRS {
- match sys::set_xattr(&path, OsStr::new(name), b"y", 0) {
- Ok(()) => break,
- Err(error) if ignorable_metadata_error(&error) => continue,
- Err(error) => return Err(error),
- }
- }
- Ok(())
- }
-
- fn create_whiteout(&self, rel: &Path) -> io::Result<()> {
- self.ensure_upper_parents(rel)?;
- let name = rel.file_name().ok_or_else(|| error(libc::EINVAL))?;
- let parent = rel.parent().unwrap_or_else(|| Path::new(""));
- let marker = self.whiteout_path(parent, name);
- if !exists(&marker) {
- OpenOptions::new()
- .write(true)
- .create_new(true)
- .mode(0o600)
- .open(marker)?;
- }
- Ok(())
- }
-
- pub fn clear_whiteout(&self, rel: &Path) -> io::Result<()> {
- self.require_visible(rel)?;
- let name = rel.file_name().ok_or_else(|| error(libc::EINVAL))?;
- let parent = rel.parent().unwrap_or_else(|| Path::new(""));
- let marker = self.whiteout_path(parent, name);
- match fs::remove_file(marker) {
- Ok(()) => Ok(()),
- Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(()),
- Err(err) => Err(err),
- }
- }
-
- pub fn create_file(&self, rel: &Path, mode: u32, flags: i32) -> io::Result {
- self.require_visible(rel)?;
- Self::validate_rel(rel)?;
- if self.resolve(rel).is_some() {
- return Err(error(libc::EEXIST));
- }
- self.record_preimage(rel)?;
- self.ensure_upper_parents(rel)?;
- self.clear_whiteout(rel)?;
- let access_mode = flags & libc::O_ACCMODE;
- let mut options = OpenOptions::new();
- options
- .read(access_mode != libc::O_WRONLY)
- .write(access_mode != libc::O_RDONLY)
- .append(flags & libc::O_APPEND != 0)
- .truncate(flags & libc::O_TRUNC != 0)
- .create_new(true)
- .mode(mode & 0o7777)
- .custom_flags(flags & !(libc::O_ACCMODE | libc::O_CREAT | libc::O_EXCL));
- options.open(self.upper_path(rel))
- }
-
- pub fn create_dir(&self, rel: &Path, mode: u32) -> io::Result<()> {
- self.require_visible(rel)?;
- if self.resolve(rel).is_some() {
- return Err(error(libc::EEXIST));
- }
- self.record_preimage(rel)?;
- let shadows_lower = self.exists_in_lower(rel);
- self.ensure_upper_parents(rel)?;
- self.clear_whiteout(rel)?;
- let path = self.upper_path(rel);
- fs::create_dir(&path)?;
- fs::set_permissions(&path, fs::Permissions::from_mode(mode & 0o7777))?;
- if shadows_lower {
- self.mark_opaque(rel)?;
- }
- Ok(())
- }
-
- pub fn create_symlink(&self, rel: &Path, target: &Path) -> io::Result<()> {
- self.require_visible(rel)?;
- if self.resolve(rel).is_some() {
- return Err(error(libc::EEXIST));
- }
- self.record_preimage(rel)?;
- self.ensure_upper_parents(rel)?;
- self.clear_whiteout(rel)?;
- std::os::unix::fs::symlink(target, self.upper_path(rel))
- }
-
- pub fn create_node(&self, rel: &Path, mode: u32, rdev: u32) -> io::Result<()> {
- self.require_visible(rel)?;
- if self.resolve(rel).is_some() {
- return Err(error(libc::EEXIST));
- }
- self.record_preimage(rel)?;
- self.ensure_upper_parents(rel)?;
- self.clear_whiteout(rel)?;
- sys::mknod(&self.upper_path(rel), mode, rdev)
- }
-
- pub fn remove(&self, rel: &Path, directory: bool) -> io::Result<()> {
- self.require_visible(rel)?;
- let resolved = self.resolve(rel).ok_or_else(|| error(libc::ENOENT))?;
- let metadata = fs::symlink_metadata(&resolved.path)?;
- if directory {
- if !metadata.is_dir() {
- return Err(error(libc::ENOTDIR));
- }
- if !self.list_names(rel)?.is_empty() {
- return Err(error(libc::ENOTEMPTY));
- }
- } else if metadata.is_dir() {
- return Err(error(libc::EISDIR));
- }
- self.record_logical_tree_mapping(rel, rel)?;
-
- if resolved.is_upper {
- if directory {
- fs::remove_dir_all(&resolved.path)?;
- } else {
- fs::remove_file(&resolved.path)?;
- }
- }
- if self.exists_in_lower(rel) {
- self.create_whiteout(rel)?;
- }
- Ok(())
- }
-
- /// Materialize the complete merged subtree and make its root opaque.
- ///
- /// This is required before renaming a lower-backed directory: a single-node
- /// copy-up would otherwise lose every child when the upper directory moves.
- pub fn materialize_tree(&self, rel: &Path) -> io::Result {
- self.require_visible(rel)?;
- let metadata = self.metadata(rel)?;
- if !metadata.is_dir() {
- return self.copy_up(rel);
- }
- let names = self.list_names(rel)?;
- self.copy_up(rel)?;
- for name in names {
- let child = Self::child(rel, &name)?;
- if self.metadata(&child)?.is_dir() {
- self.materialize_tree(&child)?;
- } else {
- self.copy_up(&child)?;
- }
- }
- self.mark_opaque(rel)?;
- Ok(self.upper_path(rel))
- }
-
- fn validate_replacement(
- &self,
- old: &Path,
- new: &Path,
- no_replace: bool,
- ) -> io::Result