diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..654e630 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,253 @@ +name: Preview release + +on: + pull_request: + push: + branches: + - feat/release-skill-sites + tags: + - v0.2.0-preview.4 + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: preview-release-${{ github.ref }} + cancel-in-progress: false + +env: + CARGO_TERM_COLOR: always + RELEASE_VERSION: 0.2.0-preview.4 + +jobs: + native-archive: + name: ${{ matrix.target }} + runs-on: ${{ matrix.runner }} + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + include: + - runner: macos-15 + target: aarch64-apple-darwin + - runner: macos-15-intel + target: x86_64-apple-darwin + - runner: ubuntu-24.04-arm + target: aarch64-unknown-linux-gnu + - runner: ubuntu-24.04 + target: x86_64-unknown-linux-gnu + steps: + - name: Check out source + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Install pinned Rust toolchain + run: >- + rustup toolchain install 1.97.1 --profile minimal + --component rustfmt --component clippy + --target "${{ matrix.target }}" + + - name: Check formatting + run: cargo +1.97.1 fmt --check + + - name: Lint all targets and features + run: cargo +1.97.1 clippy --locked --all-targets --all-features -- -D warnings + + - name: Test all targets and features + run: cargo +1.97.1 test --locked --all-targets --all-features + + - name: Build release binary + run: cargo +1.97.1 build --locked --release --target "${{ matrix.target }}" + + - name: Verify binary version + run: >- + test "$(target/${{ matrix.target }}/release/agenet --version)" + = "agenet ${RELEASE_VERSION}" + + - name: Package deterministic archive + run: | + scripts/package-release.sh \ + --binary "target/${{ matrix.target }}/release/agenet" \ + --target "${{ matrix.target }}" \ + --version "${RELEASE_VERSION}" \ + --commit "${GITHUB_SHA}" \ + --output-dir dist + + - name: Verify release archive + run: | + scripts/check-release-archive.sh \ + --archive "dist/agenet-v${RELEASE_VERSION}-${{ matrix.target }}.tar.gz" \ + --target "${{ matrix.target }}" \ + --version "${RELEASE_VERSION}" \ + --commit "${GITHUB_SHA}" + + - name: Upload immutable archive + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: native-${{ matrix.target }} + path: dist/agenet-v${{ env.RELEASE_VERSION }}-${{ matrix.target }}.tar.gz + archive: false + if-no-files-found: error + retention-days: 7 + + publish-prerelease: + name: Verify and publish immutable prerelease + if: startsWith(github.ref, 'refs/tags/') + needs: native-archive + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: write + id-token: write + attestations: write + steps: + - name: Check out exact tag source + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Install pinned Rust toolchain + run: rustup toolchain install 1.97.1 --profile minimal + + - name: Download all four native archives + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + pattern: agenet-v${{ env.RELEASE_VERSION }}-*.tar.gz + path: dist + merge-multiple: true + + - name: Require the exact archive set + run: | + find dist -mindepth 1 -maxdepth 1 -type f -printf '%f\n' \ + | LC_ALL=C sort > actual-archives.txt + cat > expected-archives.txt <- + cargo +1.97.1 build --locked + --bin agenet-release-manifest + --bin agenet-render-installer + + - name: Generate manifest and fixed installer + run: | + commit_epoch=$(git show -s --format=%ct "${GITHUB_SHA}") + published_at=$(date -u -d "@${commit_epoch}" +%Y-%m-%dT%H:%M:%SZ) + target/debug/agenet-release-manifest \ + --version "${RELEASE_VERSION}" \ + --commit "${GITHUB_SHA}" \ + --published-at "${published_at}" \ + --macos-arm64 "dist/agenet-v${RELEASE_VERSION}-aarch64-apple-darwin.tar.gz" \ + --macos-x86-64 "dist/agenet-v${RELEASE_VERSION}-x86_64-apple-darwin.tar.gz" \ + --linux-arm64 "dist/agenet-v${RELEASE_VERSION}-aarch64-unknown-linux-gnu.tar.gz" \ + --linux-x86-64 "dist/agenet-v${RELEASE_VERSION}-x86_64-unknown-linux-gnu.tar.gz" \ + --output dist/release-manifest-v1.json + target/debug/agenet-render-installer \ + --manifest dist/release-manifest-v1.json \ + --output dist/install.sh + chmod 0755 dist/install.sh + + - name: Verify the release core offline + run: | + find dist -mindepth 1 -maxdepth 1 -type f \ + ! -name SHA256SUMS -printf '%f\n' \ + | LC_ALL=C sort \ + | while IFS= read -r file; do sha256sum "dist/${file}"; done \ + | sed 's# dist/# #' > SHA256SUMS.core + scripts/verify-release.sh \ + --manifest dist/release-manifest-v1.json \ + --installer dist/install.sh \ + --checksums SHA256SUMS.core \ + --archives-dir dist \ + --version "${RELEASE_VERSION}" \ + --commit "${GITHUB_SHA}" \ + --renderer target/debug/agenet-render-installer + + - name: Add guides, Skill, and release notes + run: | + cp docs/bootstrap/agent-node-setup.md dist/agent-bootstrap.md + cp docs/bootstrap/agent-node-setup.en.md dist/agent-bootstrap.en.md + scripts/package-bootstrap-skill.sh \ + --version "${RELEASE_VERSION}" \ + --output "dist/agenet-node-bootstrap-v${RELEASE_VERSION}.tar.gz" + cp "docs/releases/v${RELEASE_VERSION}.md" \ + "dist/release-notes-v${RELEASE_VERSION}.md" + find dist -mindepth 1 -maxdepth 1 -type f \ + ! -name SHA256SUMS -printf '%f\n' \ + | LC_ALL=C sort \ + | while IFS= read -r file; do sha256sum "dist/${file}"; done \ + | sed 's# dist/# #' > dist/SHA256SUMS + scripts/verify-release.sh \ + --manifest dist/release-manifest-v1.json \ + --installer dist/install.sh \ + --checksums dist/SHA256SUMS \ + --archives-dir dist \ + --version "${RELEASE_VERSION}" \ + --commit "${GITHUB_SHA}" \ + --renderer target/debug/agenet-render-installer \ + --complete \ + --release-dir dist + + - name: Attest macOS arm64 archive + uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d # v4.2.1 + with: + subject-path: dist/agenet-v${{ env.RELEASE_VERSION }}-aarch64-apple-darwin.tar.gz + + - name: Attest macOS x86_64 archive + uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d # v4.2.1 + with: + subject-path: dist/agenet-v${{ env.RELEASE_VERSION }}-x86_64-apple-darwin.tar.gz + + - name: Attest Linux arm64 archive + uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d # v4.2.1 + with: + subject-path: dist/agenet-v${{ env.RELEASE_VERSION }}-aarch64-unknown-linux-gnu.tar.gz + + - name: Attest Linux x86_64 archive + uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d # v4.2.1 + with: + subject-path: dist/agenet-v${{ env.RELEASE_VERSION }}-x86_64-unknown-linux-gnu.tar.gz + + - name: Publish once or prove exact idempotency + env: + GH_TOKEN: ${{ github.token }} + run: | + tag="${GITHUB_REF_NAME}" + if gh release view "${tag}" >/dev/null 2>&1; then + comparison=$(mktemp -d) + trap 'rm -rf -- "${comparison}"' EXIT + gh release download "${tag}" --dir "${comparison}" + find dist -mindepth 1 -maxdepth 1 -type f -printf '%f\n' \ + | LC_ALL=C sort > expected-assets.txt + find "${comparison}" -mindepth 1 -maxdepth 1 -type f -printf '%f\n' \ + | LC_ALL=C sort > existing-assets.txt + diff -u expected-assets.txt existing-assets.txt + while IFS= read -r asset; do + cmp "dist/${asset}" "${comparison}/${asset}" + done < expected-assets.txt + release_state=$(gh release view "${tag}" \ + --json isPrerelease,isDraft \ + --jq '"\(.isPrerelease) \(.isDraft)"') + test "${release_state}" = 'true false' + else + gh release create "${tag}" dist/* \ + --prerelease \ + --verify-tag \ + --title "AgenNet ${tag} Developer Preview" \ + --notes-file "docs/releases/v${RELEASE_VERSION}.md" + fi + + - name: Upload complete verified release bundle + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: preview-release-${{ env.RELEASE_VERSION }} + path: dist/* + if-no-files-found: error + retention-days: 30 diff --git a/.gitignore b/.gitignore index 5b27839..86f5915 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,6 @@ target/ *.key *.pem +*.token *.log *.jsonl - diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e8dc7a1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3857 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "age" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd290633c2482479f70f6d1d96ae0e9f52c6a26cd5859edd47ee1fe33fc89f26" +dependencies = [ + "age-core", + "base64 0.22.1", + "bech32", + "chacha20poly1305", + "cipher", + "cookie-factory", + "hkdf", + "hmac 0.12.1", + "hpke", + "i18n-embed", + "i18n-embed-fl", + "lazy_static", + "ml-kem", + "nom 8.0.0", + "p256", + "pin-project", + "rand 0.8.7", + "rust-embed", + "scrypt", + "sha2 0.10.9", + "sha3", + "subtle", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "age-core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d4375964d1501e5f1b32aef2ead573913893ff238448d4e9fdf1522d828656" +dependencies = [ + "base64 0.22.1", + "bech32", + "chacha20poly1305", + "cookie-factory", + "hkdf", + "hpke", + "io_tee", + "nom 8.0.0", + "rand 0.8.7", + "secrecy", + "sha2 0.10.9", +] + +[[package]] +name = "agenet" +version = "0.2.0-preview.4" +dependencies = [ + "age", + "axum", + "axum-server", + "base64 0.23.1", + "bytes", + "clap", + "directories", + "dotenvy", + "ed25519-dalek", + "getrandom 0.4.3", + "hmac 0.13.0", + "http-body-util", + "ipnet", + "libc", + "pem", + "plist", + "proptest", + "rcgen", + "reqwest", + "rpassword", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "sha2 0.11.0", + "tempfile", + "time", + "tokio", + "tokio-rustls", + "tower", + "tracing", + "tracing-subscriber", + "url", + "uuid", + "x509-parser", + "zeroize", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "arc-swap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "asn1-rs" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom 7.1.3", + "num-traits", + "rusticata-macros", + "thiserror 2.0.20", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "aws-lc-rs" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", + "pkg-config", +] + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-server" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1df331683d982a0b9492b38127151e6453639cd34926eb9c07d4cd8c6d22bfc" +dependencies = [ + "arc-swap", + "bytes", + "either", + "fs-err", + "http", + "http-body", + "hyper", + "hyper-util", + "pin-project-lite", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + +[[package]] +name = "bech32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec 0.8.0", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array 0.4.14", + "zeroize", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20 0.9.1", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", + "zeroize", +] + +[[package]] +name = "clap" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" +dependencies = [ + "futures", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array 0.4.14", + "rand_core 0.10.1", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "curve25519-dalek-derive", + "digest 0.11.3", + "fiat-crypto 0.3.0", + "rand_core 0.10.1", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid 0.9.6", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom 7.1.3", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "const-oid 0.10.2", + "crypto-common 0.2.2", + "ctutils", + "zeroize", +] + +[[package]] +name = "directories" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "ed25519" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" +dependencies = [ + "curve25519-dalek 5.0.0", + "ed25519", + "rand_core 0.10.1", + "sha2 0.11.0", + "signature", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "hkdf", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + +[[package]] +name = "find-crate" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a98bbaacea1c0eb6a0876280051b892eb73594fd90cf3b20e9c817029c57d2" +dependencies = [ + "toml", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" + +[[package]] +name = "fluent" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8137a6d5a2c50d6b0ebfcb9aaa91a28154e0a70605f112d30cb0cd4a78670477" +dependencies = [ + "fluent-bundle", + "unic-langid", +] + +[[package]] +name = "fluent-bundle" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01203cb8918f5711e73891b347816d932046f95f54207710bda99beaeb423bf4" +dependencies = [ + "fluent-langneg", + "fluent-syntax", + "intl-memoizer", + "intl_pluralrules", + "rustc-hash", + "self_cell", + "smallvec", + "unic-langid", +] + +[[package]] +name = "fluent-langneg" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eebbe59450baee8282d71676f3bfed5689aeab00b27545e83e5f14b1195e8b0" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "fluent-syntax" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198" +dependencies = [ + "memchr", + "thiserror 2.0.20", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs-err" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91aa448ca50d7e79433bdf3ee8d99215430d2ec02ade5aefab2a073a1822e8a" +dependencies = [ + "autocfg", + "tokio", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac 0.12.1", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + +[[package]] +name = "hpke" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4917627a14198c3603282c5158b815ad5534795451d3c074b53cf3cee0960b11" +dependencies = [ + "aead", + "aes-gcm", + "chacha20poly1305", + "digest 0.10.7", + "generic-array", + "hkdf", + "hmac 0.12.1", + "p256", + "rand_core 0.6.4", + "sha2 0.10.9", + "subtle", + "zeroize", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hybrid-array" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2d35805454dc9f8662a98d6d61886ffe26bd465f5960e0e55345c70d5c0d2a9" +dependencies = [ + "typenum", +] + +[[package]] +name = "hybrid-array" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +dependencies = [ + "typenum", +] + +[[package]] +name = "hyper" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "i18n-config" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e06b90c8a0d252e203c94344b21e35a30f3a3a85dc7db5af8f8df9f3e0c63ef" +dependencies = [ + "basic-toml", + "log", + "serde", + "serde_derive", + "thiserror 1.0.69", + "unic-langid", +] + +[[package]] +name = "i18n-embed" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a217bbb075dcaefb292efa78897fc0678245ca67f265d12c351e42268fcb0305" +dependencies = [ + "arc-swap", + "fluent", + "fluent-langneg", + "fluent-syntax", + "i18n-embed-impl", + "intl-memoizer", + "log", + "parking_lot", + "rust-embed", + "thiserror 1.0.69", + "unic-langid", + "walkdir", +] + +[[package]] +name = "i18n-embed-fl" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "602e6bd30c3db2749e13e38b363a3d98d9d41de1d8de7a79c31bb69e45b47cda" +dependencies = [ + "find-crate", + "fluent", + "fluent-syntax", + "i18n-config", + "i18n-embed", + "proc-macro-error3", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", + "unic-langid", +] + +[[package]] +name = "i18n-embed-impl" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2cc0e0523d1fe6fc2c6f66e5038624ea8091b3e7748b5e8e0c84b1698db6c2" +dependencies = [ + "find-crate", + "i18n-config", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "intl-memoizer" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310da2e345f5eb861e7a07ee182262e94975051db9e4223e909ba90f392f163f" +dependencies = [ + "type-map", + "unic-langid", +] + +[[package]] +name = "intl_pluralrules" +version = "7.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972" +dependencies = [ + "unic-langid", +] + +[[package]] +name = "io_tee" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b3f7cef34251886990511df1c61443aa928499d598a9473929ab5a90a527304" + +[[package]] +name = "ipnet" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" +dependencies = [ + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror 2.0.20", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "kem" +version = "0.3.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8645470337db67b01a7f966decf7d0bafedbae74147d33e641c67a91df239f" +dependencies = [ + "rand_core 0.6.4", + "zeroize", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libredox" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "ml-kem" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de49b3df74c35498c0232031bb7e85f9389f913e2796169c8ab47a53993a18f" +dependencies = [ + "hybrid-array 0.2.3", + "kem", + "rand_core 0.6.4", + "sha3", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "elliptic-curve", + "primeorder", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "hmac 0.12.1", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plist" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" +dependencies = [ + "base64 0.22.1", + "indexmap", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro-error-attr3" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0084e6206a967a2dad822180626b2f6b07a3b379325e8f1ec0438e33a469ba7" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error3" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cf066225f2373bc711684792b69bdeac0356019b007e721090c24d92d5d5a50" +dependencies = [ + "proc-macro-error-attr3", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec 0.8.0", + "bitflags", + "num-traits", + "rand 0.9.5", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.20", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.20", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20 0.10.1", + "getrandom 0.4.3", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rcgen" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091e7a8e7d86e6feb87a27ce8e2cba29d49eff9507afeebefab7eeb2ca667fb4" +dependencies = [ + "aws-lc-rs", + "pem", + "rustls-pki-types", + "time", + "x509-parser", + "yasna", + "zeroize", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.20", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rpassword" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da316a15f47e3d053de9cb2c439650bd8fa4aaeb9365f2e5f27f492ff73c196" +dependencies = [ + "libc", + "rtoolbox", + "windows-sys 0.61.2", +] + +[[package]] +name = "rtoolbox" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "rust-embed" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e7760e252aaba7b09f4be00e36476cf585bdb68a53552ac954cdf504ab4bc9" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcfc4d6f53af43755f7a723e4b6b8794fcce052a178dd8c6c1dadc5f5343097" +dependencies = [ + "mime_guess", + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.119", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ffa149f6aa81b58a5b3011d01a857c4ed12c7a732d2c51947a4c7c692185f0" +dependencies = [ + "sha2 0.11.0", + "walkdir", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom 7.1.3", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "salsa20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" +dependencies = [ + "cipher", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scrypt" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" +dependencies = [ + "pbkdf2", + "salsa20", + "sha2 0.10.9", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "self_cell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "rand_core 0.10.1", +] + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl 2.0.20", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "serde_core", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "libc", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unic-langid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05" +dependencies = [ + "unic-langid-impl", +] + +[[package]] +name = "unic-langid-impl" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658" +dependencies = [ + "serde", + "tinystr", +] + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek 4.1.3", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "aws-lc-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom 7.1.3", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.20", + "time", +] + +[[package]] +name = "yasna" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" +dependencies = [ + "bit-vec 0.9.1", + "time", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "serde", + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..66c6ad9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,77 @@ +[package] +name = "agenet" +version = "0.2.0-preview.4" +edition = "2024" +rust-version = "1.97.1" +description = "Experimental AgenNet private-overlay coordination substrate" +license = "MIT" + +[lib] +name = "agenet" +path = "src/lib.rs" + +[features] +default = [] +cli-test-fixture = [] + +[[bin]] +name = "agenet" +path = "src/main.rs" + +[[bin]] +name = "agenet-release-manifest" +path = "src/bin/agenet-release-manifest.rs" + +[[bin]] +name = "agenet-render-installer" +path = "src/bin/agenet-render-installer.rs" + +[[bin]] +name = "agenet-sync-public-guides" +path = "src/bin/agenet-sync-public-guides.rs" + +[dependencies] +age = "=0.12.1" +axum = "=0.8.9" +axum-server = { version = "=0.8.0", features = ["tls-rustls"] } +base64 = "=0.23.1" +bytes = "=1.12.1" +clap = { version = "=4.6.6", features = ["derive"] } +directories = "=6.0.0" +dotenvy = "=0.15.7" +ed25519-dalek = { version = "=3.0.0", features = ["rand_core"] } +getrandom = "=0.4.3" +hmac = { version = "=0.13.0", features = ["zeroize"] } +ipnet = { version = "=2.12.1", features = ["serde"] } +libc = "=0.2.189" +pem = "=3.0.6" +rcgen = { version = "=0.14.9", default-features = false, features = ["aws_lc_rs", "pem", "x509-parser", "zeroize"] } +reqwest = { version = "=0.13.4", features = ["json", "query"] } +rpassword = "=7.5.4" +rustls = "=0.23.43" +rustls-pemfile = "=2.2.0" +serde = { version = "=1.0.229", features = ["derive"] } +serde_json = "=1.0.151" +sha2 = "=0.11.0" +time = "=0.3.55" +tokio = { version = "=1.53.1", features = ["full", "test-util"] } +tokio-rustls = "=0.26.4" +tracing = "=0.1.44" +tracing-subscriber = { version = "=0.3.20", features = ["env-filter", "fmt"] } +tower = "=0.5.3" +uuid = { version = "=1.24.0", features = ["serde", "v4"] } +url = { version = "=2.5.8", features = ["serde"] } +x509-parser = "=0.18.1" +zeroize = "=1.9.0" + +[target.'cfg(target_os = "macos")'.dependencies] +plist = "=1.10.0" + +[dev-dependencies] +http-body-util = "=0.1.5" +proptest = "=1.11.0" +tempfile = "=3.27.0" + +[profile.release] +strip = true +lto = "thin" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..dd84a4e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Nexa Language + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 4939e6d..0d87d64 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,201 @@ # AgenNet -AgenNet is an experimental Agent-native coordination substrate built on existing network transports. The first milestone is a real, local, multi-process loopback network that proves dynamic capability discovery, signed bilateral contracts, scoped artifact access, independent verification, and evidence-gated acceptance. +AgenNet is an experimental Agent-native coordination substrate built on +existing network transports. **v0.2.0-preview.4 is a Developer Preview — +physical acceptance pending.** It proves dynamic Capability discovery, signed +bilateral Contracts, scoped Artifact access, independent verification, and +evidence-gated acceptance through real multi-process and mTLS paths; it does +not yet claim completed physical multi-device validation. + +## Install the Developer Preview + +Use the [Chinese](docs/install/index.md) or +[English](docs/install/index.en.md) checksum-verifying installation guide. +macOS arm64/x86_64 and Linux arm64/x86_64 are supported. Windows users run the +Linux build inside WSL2; native Windows is unsupported. Agent operators can use +the fixed [node bootstrap guide](docs/bootstrap/agent-node-setup.md) or the +packaged `agenet-node-bootstrap` Skill. Every Invitation and passphrase stays +inside the human's local controlling TTY. + +## Long-term vision + +AgenNet's North Star is to enable authorized coordination among participating +Agents and network-reachable resources without requiring a global operator, +then grow that network into an Agent Society capable of organizing specialized +intelligence, balancing resources, managing conflicts, and helping people +pursue complex goals. We believe such a society may become a practical path +toward collective AGI. This is a long-term research hypothesis, not a claim +about the v0.2 implementation. + +The living vision—including horizontal Agent links, vertical knowledge and +institutional inheritance, an Agent Library, School, organizations, +maintenance, public-safety mechanisms, and quality-of-service transport—is in +[`docs/vision/agent-society.md`](docs/vision/agent-society.md). + +## v0.2 boundary and security dependencies + +The package is now version `0.2.0-preview.4`. New v0.2 objects use the explicit +`agenet.kernel.v0.2` kernel version. `agenet.kernel.v0.1` remains identifiable +only for migration diagnostics; it is not an implicit compatibility mode and +does not authorize a v0.2 effect endpoint. The v0.2 wire envelope carries a +Root-to-Authority-to-Node credential chain, validates its Domain, scope, +profile, role, issuer, and lifetime before deserializing signed business +payload bytes, and rejects direct-root v0.1 credentials with +`MigrationRequiredV1Credential`. + +There are no external v0.1 consumers in this repository's supported release +surface. The loopback demo now provisions fresh v0.2 chains on every run. +Retained v0.1 credential state is read only for migration diagnosis and must +be regenerated; there is no v0.1 credential issuance API or v0.2 effect-path +fallback. + +The v0.2 baseline pins the TLS server, certificate, encryption, protected +input, HMAC, network-prefix, platform-directory, clock, and macOS plist +dependencies required by the subsequent bootstrap tasks. The review record, +including licenses, transitive footprint, and removal boundaries, is in +[`docs/security/dependency-review-v0.2.md`](docs/security/dependency-review-v0.2.md). + +## Provisional local bootstrap state + +Task 9 introduces a deliberately versioned local persistence boundary; it is +not a promise that these schemas or phase choices will never change. +`agenet.node-config` schema 2 is a bounded, deny-unknown-fields JSON document. +It carries the Domain, bootstrap profile, network boundary, Directory seeds, +Authority endpoint, and revocation endpoint. Private-overlay endpoints are +exact IP-literal HTTPS origins with no credentials, query, fragment, DNS name, +or non-root path. Plain HTTP is accepted only when the persisted boundary is +explicitly loopback. + +Current-host roots are resolved through the `directories` crate. macOS uses +`~/Library/Application Support/AgenNet/`; Linux uses +`${XDG_CONFIG_HOME:-~/.config}/agenet/` and +`${XDG_STATE_HOME:-~/.local/state}/agenet/`. Config, credential chain, Ed25519 +signing key, TLS certificate/key/CA, service metadata, revocation cache, +journal, and process lock have separate versioned names. Managed directories +must be current-user `0700` non-symlink directories; owner-only files must be +regular current-user `0600` files and are opened with no-follow, nonblocking, +bounded reads. Startup material is returned only after credential +Root/domain/role/profile/time, signing-key, TLS chain/key, NodeId, exact IP SAN, +and certificate-time validation succeeds. Each Directory seed is the exact +versioned pair `{ endpoint, node_id }`; URL-only legacy seeds fail closed. + +The credential, signing key, certificate, TLS key, and CA are intentionally +separate files, not a claimed multi-file transaction. A writer must hold the +bootstrap state lock, publish and validate the complete set, and only then +append `CredentialIssued`. A crash may leave a partial set, but restart loads +every file and fails closed instead of deleting credentials or guessing which +file won. Task 10 owns command-level retry/reconciliation of that incomplete +set; Task 9 provides only the validated persisted-bundle boundary. + +The local journal records this forward path: + +```text +Absent → BinaryInstalled → ReadyForEnrollment → CredentialIssued + → ServicePrepared → Registered → Healthy +``` + +The only backward compensation is `ServicePrepared → CredentialIssued`; it +removes no credential material. Task 10 never records `ServicePrepared`, because +Task 11 must first publish a real service artifact. `Leave` moves any installed, +non-Left phase to `Left`. Every record contains a unique operation +ID, sequence, previous hash, transition, and checksum beneath a separate +versioned/checksummed header. Exact operation replay is idempotent; changed +reuse, illegal transitions, incomplete lines, unknown versions, corruption, +and bounds violations fail closed. One owner-only nonblocking process lock is +held for the store lifetime. An uncertain durable append poisons further +mutation until restart; atomic replacement failures after publish are likewise +reported as uncertain so restart can reconcile the visible final file. +Journal schema 2 encodes this order and rejects schema 1 rather than +reinterpreting its contradictory phase semantics. The journal is provisional +newline-framed JSONL, not binary framing. Its lock, +create/open, replay, header sync, and later appends remain anchored to one +verified owner-only directory descriptor and one journal descriptor, so a +pathname replacement cannot redirect publication between validation and sync. +Temporary private-material files are guarded from creation through publication: +any metadata, write, flush, or file-sync error unlinks the temporary name via +the pinned parent descriptor. Successful rename/link publication disarms that +cleanup before the parent-directory sync, so an uncertain post-publish sync +error never deletes the final file. A process crash can still leave a `0600` +temporary inode; automatic wildcard cleanup is intentionally unsupported. + +New journal creation exposes its owner-only final name before writing and +syncing the versioned header. If header write, file sync, or parent-directory +sync is uncertain, opening the store fails and the file is retained. Restart +accepts only a complete valid header and otherwise fails closed; explicit +operator/Task 10 repair is required. AgenNet does not automatically delete the +file because a complete header may already be durable even when sync reported +an error. + +## Login-scoped bootstrap supervisor + +After `domain init` or `node join` has durably reached `CredentialIssued`, the +operator may explicitly run: + +```text +agenet node start +agenet node status --output json +agenet node stop +``` + +`node start` atomically publishes a real user-service definition, records +`ServicePrepared`, and only then asks the login-scoped service manager to start +it. macOS uses +`~/Library/LaunchAgents/org.nexa-language.agenet.plist`; Linux uses +`~/.config/systemd/user/agenet.service`. Every ancestor of that absolute path +is opened without following symlinks and must be root/current-user owned and +not group/world writable. An activation failure rolls the journal back to +`CredentialIssued` only after the platform manager verifies the process is +stopped and the exact definition is absent. Otherwise it retains +`ServicePrepared`, returns `ServiceRollbackIncomplete`, and keeps all +credential material. A later start republishes and reconciles an uncertain +post-delete state. Repeated start and stop operations do not advance to +`Registered` or `Healthy`. + +The service runs the internal bootstrap supervisor. It strictly loads +the persisted config, Root/Authority/Node credential chain, signing key, peer +TLS identity, and schema-2 journal; holds the single-instance journal lock; and +exits gracefully on termination. Task 12 attaches the v0.2 peer runtime to this +same entrypoint. It loads the current revocation cache (refreshing through the +pinned Authority CA if necessary), derives handlers only from verified signed +roles, binds the exact configured address, performs an mTLS health probe, and +registers signed provider manifests before publishing an owner-only readiness +artifact. Registration/startup failure withdraws readiness and reaps the +listener. `runtime_ready` means this base peer runtime is listening under +current policy. Production `domain init` issues the founding node exactly the +signed `Directory` and `Requester` roles with an empty provider capability +ceiling and creates a separate owner-only local-control token. When both are +present, the runtime merges the Requester's signed Artifact route into the +private-overlay mTLS peer listener and starts a separate dynamic loopback-only +pursuit listener. Its owner-only ready record contains a local endpoint and +process generation, never the token. Its unauthenticated `/healthz` is +loopback-only and returns only the Node ID, process generation, and readiness +booleans. Every production peer `/healthz` is mTLS-only and returns only its +public Node ID, process generation, runtime readiness, and current revocation +boolean. Evidence collection self-probes that exact configured peer endpoint +on both A and B with the validated local certificate/key/CA bundle, requires an +exact TLS Node/process match, and independently requires the platform user +service to report a running process. Device A also requires its local-control +health to match ready and service metadata. A token without the signed role +fails closed; a signed role without the token leaves the Directory available +but disables pursuits. Provider enrollment creates no local-control token. +Bootstrap profile is never used as an authorization role or capability grant. + +A verified founding Directory additionally loads a typed founding-only +Authority runtime from the existing owner-only Authority credential/signing +key, CA certificate/key, invitation state, enrollment-result directory, and +revocation Authority state. It never opens the Domain Root private keystore. +The supervisor signs a short-lived exact-IP server leaf, self-checks separate +server-auth TLS enrollment and revocation listeners, keeps revocation snapshots +fresh with the online Authority key, and cancels all listeners if any required +surface fails. Non-Directory credentials never load these Authority files. + +These services persist only after the owning user logs in. AgenNet does not use +`sudo`, install a system service, enable Linux linger, embed an environment +file, or put secrets in the service definition. ## MVP boundary -The MVP runs four independent processes on different `127.0.0.1` ports: +The demo runs four independent processes on dynamic loopback ports: - Directory/Router - Requester Agent @@ -13,18 +204,77 @@ The MVP runs four independent processes on different `127.0.0.1` ports: The first real Capability is `source.metrics.v1`: it computes a source Artifact's SHA-256 digest, byte count, line count, and non-empty line count. The Verifier independently recomputes the same metrics. Delivery does not become `Accepted` until verification succeeds. -The MVP validates loopback coordination semantics only. It does **not** validate multi-machine transport security, TLS, distributed failover, quota accounting, arbitrary code sandboxing, or Internet-scale discovery. +The demo now uses Authority-CA mutual TLS for every peer hop, exact-IP SANs, +NodeId-bound signed envelopes, current revocation snapshots, and dynamically +signed Directory results. It probes `127.0.0.2` through `127.0.0.5`; hosts that +do not expose those aliases fall back to distinct ports on `127.0.0.1`. The +summary labels these as `simulated_loopback_aliases_mtls` or +`loopback_ports_mtls` beneath `loopback_harness`. Neither is physical +multi-machine evidence. It does +**not** validate distributed failover, quota accounting, arbitrary code +sandboxing, or Internet-scale discovery. ## Intended command ```bash cargo run -- demo \ - --env-file /Users/bytedance/proj/bandai/Walkman/.env \ + --env-file /path/to/model.env \ --artifact fixtures/sample.rs ``` The environment file is read in place. It is never copied, logged, or committed. +For a production founding node on a private overlay, the operator submits the +same source-metrics flow through the local Requester boundary: + +```bash +agenet pursuit run \ + --env-file /path/to/model.env \ + --artifact fixtures/sample.rs \ + --output json +``` + +This command accepts no Directory, Executor, or Verifier endpoint. It retains +only the allowlisted `OPENAI_BASE_URL`, `OPENAI_API_KEY`, and `VLM_MODEL` +entries while parsing the file and immediately discards unrelated entries. It makes one strict +OpenAI-compatible decision with at most one format repair and no deterministic +fallback, then sends the decision and bounded Artifact bytes over the +authenticated loopback listener. Provider endpoints are learned only from +signed Directory Manifests. The physical acceptance runbook and redacted +evidence boundary are in +[`docs/testing/two-device-acceptance.md`](docs/testing/two-device-acceptance.md). + +Required Walkman alias names are `OPENAI_BASE_URL`, `OPENAI_API_KEY`, and `VLM_MODEL`. For this command, `OPENAI_BASE_URL` is the complete ModelHub `gemini_multimodal_inline_v1` endpoint: the adapter does not append a route, places the credential only in the `ak` query parameter, and sends inline text content. Only the Requester child receives these three variables. The other three children are started with a cleared environment. The demo never falls back to `DeterministicDecisionAdapter`; an invalid model response fails explicitly after one format-repair request. + +The successful command prints one JSON summary containing: + +- four distinct PIDs, HTTPS loopback addresses, Node IDs, and state directories; +- source and verification Contract IDs; +- `Proposed → Active → Running → Delivered → Accepted`; +- the immutable Artifact hash and both metric results; +- LLM call count, peer HTTP request/byte counters, and phase timings; +- the retained `.local/demo//` directory for inspection. + +Private keys and the local control token are stored with mode `0600`. Runtime state, logs, private keys, tokens, and JSONL journals are ignored by Git. Peer payloads never accept a filesystem path. + +## Architecture + +```text +demo harness (HTTPS/mTLS; loopback aliases when assigned) + ├─ Directory loopback:dynamic + ├─ Requester loopback:dynamic ── real LLM decision + ├─ Executor loopback:dynamic ── source.metrics.v1 + └─ Verifier loopback:dynamic ── source.metrics.verify.v1 + +Requester → Directory → signed CandidateSet +Requester → Executor → bilateral source Contract → Delivered Evidence +Requester → Directory → signed CandidateSet +Requester → Verifier → parent-linked verification Contract → Delivered Evidence +Requester → Executor → signed Accepted Event +``` + +`agenet demo` is provisioning and test scaffolding, not a control plane. Its four credentials are least privilege: Directory-only, Requester-only, Executor-only with the executor capability ceiling, and Verifier-only with the verifier ceiling. The Requester child receives only the Directory seed; Executor and Verifier endpoints are learned from signed Capability Manifests. + ## Development gates ```bash @@ -33,3 +283,103 @@ cargo clippy --all-targets --all-features -- -D warnings cargo test --all-targets ``` +Tests include pure protocol/property checks, storage replay, Axum `oneshot` authorization and limits, Reqwest timeout/error handling, LLM JSON repair and secret sentinels, metric edge cases, and a real four-child-process loopback demo backed by a clearly synthetic local completion server. + +## Security boundary + +Plain HTTP remains restricted to loopback. A non-loopback peer endpoint must +use HTTPS with one explicit Authority CA, a required client certificate, an +exact IP SAN, and one canonical noncritical AgenNet NodeId extension. Inbound +requests bind that certificate NodeId to the signed envelope issuer; outbound +clients additionally require the expected remote NodeId from a verified seed +or signed Capability Manifest. They do not use system roots, ambient proxies, +or redirects. Revoked certificates fail at the TLS boundary; stale revocation +state preserves health diagnostics while effectful handlers fail closed. +Signed responses complete credential-chain, role, domain, expiry, and signature +verification before their verified issuer is compared with the TLS peer NodeId; +an invalid signed response is never classified as a TLS identity mismatch. +Client construction also requires its own leaf to contain exactly the declared +local boundary IP, preventing a valid peer identity issued for one overlay +address from being reused from another configured address. +Both directions require the SAN extension to contain exactly one entry: the +expected IP. A matching IP plus any additional IP or DNS identity is rejected. +Peer private-key PEM contains exactly one PKCS#8 block; mixed or trailing key +blocks fail configuration. Public `/v0` routers also fail closed when neither +Rustls certificate metadata nor connection-layer loopback metadata is present, +so embedding a peer router in a plain non-loopback server cannot silently skip +the mTLS identity boundary. + +The real TLS tests currently use dynamic loopback ports to exercise the same +Rustls/Reqwest handshake path. This is not evidence of physical multi-machine +reachability. Separate OS processes and state directories are not claimed as a +secure sandbox. The workload does not execute shell commands. A future +`project.build_test.v1` adapter must use Docker, a VM, or a platform sandbox +before accepting untrusted code. + +## Developer Preview bootstrap CLI + +One typed CLI path is being added for private Tailscale and WireGuard Domains: + +```text +agenet domain init --network tailscale --bind-ip +agenet domain init --network wireguard --bind-ip \ + --allowed-cidr +agenet invite create --profile --ttl 10m +agenet node join [--bind-ip ] +``` + +Root passphrases and complete invitations use only the controlling terminal; +they are not accepted through argv, environment variables, JSON, or ordinary +stdin. `node join` returns `credential_issued` instead of claiming registration +or health. The current join result has no next command; +`domain init` advertises only the existing `invite create` command. + +After enrollment, the lifecycle surface is: + +```text +agenet node start|stop|status +agenet credential renew +agenet node revoke +agenet node leave +agenet uninstall [--purge] +agenet node doctor --output +``` + +Renewal keeps the Node Ed25519 identity and rotates the TLS private key through +a fresh CSR. Startup identity is stored as complete, owner-only UUID generations +with exact file hashes. A single atomic active pointer selects one generation; +once present, the runtime never combines new and legacy files. Renewal restarts +the actual user service and requires its readiness plus new-identity mTLS health +before retired-key cleanup. Cleanup uncertainty retains old material and emits a +warning rather than claiming deletion. + +Identity lifecycle mutations are serialized at two levels. Credential renewal +and destructive purge hold one owner-only `identity-operation-v1.lock` across +the entire workflow. Startup readers take a short shared advisory lock on the +owner-only identity-generation directory, while pointer publish/rollback, +inactive cleanup, and purge take it exclusively. The exclusive pointer lock is +released before restarting the user service, so HostRuntime can load the newly +selected generation without deadlocking. These locks coordinate honest AgenNet +processes under one account; a malicious same-euid process that ignores +advisory locks remains outside the v0.2 threat boundary. + +`node revoke` is available only on the founding administrative host and requires +a controlling TTY, exact NodeId confirmation, and hidden Domain Root unlock. +`node leave` stops the service but retains identity, config, credentials, +journals, and audit state; Directory outage leaves a durable pending departure. +The pending record contains one exact signed request and operation ID reused by +later `node leave` invocations, including after local `Left`. A Directory-signed +receipt is durably validated and stored before pending state is cleared; missing +or invalid receipt state is never reported as a recorded departure. +Default uninstall retains all state and removes a binary only when its recorded +path, owner, mode, device, inode, size, hash, version, basename, and approved +per-user installation root still match. `--purge` is destructive, requires both +the exact NodeId and `PURGE` on the controlling TTY, and always retains Domain +Root plus founding Authority/administrative material. + +Doctor is read-only and bounded. JSON output is deterministic and contains only +stable check code, `ok|warn|error|skipped`, sanitized message, remediation link, +and a stable overall exit code: 0 for all OK/skipped, 1 for warnings, 2 for any +error, and 3 only when report output itself fails. It does not refresh state. + +Physical two-device reachability and setup automation remain Task 14 gates. diff --git a/ROADMAP.md b/ROADMAP.md index 329aa77..2edc8d2 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,9 +1,536 @@ # ROADMAP +## 2026-08-15 — Publish the first installable Developer Preview + +- **Change**: Published `v0.2.0-preview.4` as an immutable GitHub prerelease with four native archives, SHA-256 checksums, fixed installer, bilingual Agent guides, packaged bootstrap Skill, release manifest, notes, and four provenance attestations; deployed the synchronized GPT Sites version publicly. +- **Files**: This roadmap; release source is commit `088585e` and site source is commit `1233b86`. +- **Evidence**: [GitHub Release](https://github.com/Nexa-Language/AgenNet/releases/tag/v0.2.0-preview.4), [public site](https://agenet-society.fernandez-owen.chatgpt.site), and successful tag workflow `31890126601`. The live homepage was fetched after deployment and contains the fixed `v0.2.0-preview.4` identity. +- **Boundary**: This is a Developer Preview. Fresh WSL2 one-sentence Agent installation and physical two-device overlay acceptance remain the next external tests; neither is claimed complete here. + +## 2026-08-15 — Advance the publishable candidate to preview.4 + +- **Change**: Advanced all current release, installer, guide, Skill, test, and site version sources to `v0.2.0-preview.4` after preserving the failed `preview.3` tag for audit. +- **Files**: Versioned release surfaces, the renamed release note, and this roadmap. +- **Decision reason**: An already-pushed tag is never force-moved. The runtime content is unchanged; only the corrected publication tooling and immutable version identity advance. +- **Boundary**: Physical two-device and clean WSL2 Agent acceptance remain pending and are still stated on every public surface. + +## 2026-08-15 — Keep binary smoke tests on native runners + +- **Change**: Added an explicit structure-only mode for the aggregate archive verifier while retaining executable `agenet --version` smoke tests in every native matrix job. +- **Files**: `scripts/check-release-archive.sh`, `scripts/verify-release.sh`, and this roadmap. +- **Root cause / classification**: **CI 架构盲区**. The Linux publish job correctly downloaded all four archives but then attempted to execute macOS and ARM binaries while rechecking them, producing `ReleaseBinarySmokeFailed` after every native runner had already executed its own binary successfully. +- **Prevention**: Native jobs remain responsible for executable compatibility. The aggregate job independently validates the exact archive set, deterministic structure, metadata, source files, sizes, and cryptographic hashes without cross-executing foreign binaries. + +## 2026-08-15 — Advance the unpublished candidate to preview.3 + +- **Change**: Advanced every package, manifest, installer, guide, Skill, site, test, and workflow version source to `v0.2.0-preview.3` after the `preview.2` publication job failed before creating a GitHub Release. +- **Files**: Versioned release surfaces, the renamed release note, and this roadmap. +- **Decision reason**: The already-pushed `preview.2` tag is retained instead of being force-moved. A new tag gives operators an immutable source-to-asset identity while preserving the failed run for audit. +- **Boundary**: This is a publication-only version advance. Runtime behavior and the honest “physical acceptance pending” status are unchanged. + +## 2026-08-15 — Match the uploaded release artifact names + +- **Change**: Matched the publish job's download pattern to the immutable native archive artifact names emitted by `upload-artifact` in non-archive mode. +- **Files**: `.github/workflows/release.yml` and this roadmap. +- **Root cause / classification**: **CI API 语义盲区**. With `archive: false`, the pinned upload action publishes each file under its archive filename rather than the configured logical `native-*` name. The four platform jobs succeeded, but the publish job's old pattern selected no artifacts and failed the exact-set check. +- **Prevention**: The download pattern now includes the fixed version and archive suffix, while the following exact-set comparison remains the fail-closed authority for the four required targets. + +## 2026-08-15 — Keep cross-platform LaunchAgent fixtures secure + +- **Change**: Moved the non-native LaunchAgent manager fixtures below the runner-owned repository so Linux executes both positive publication paths and the intended symlink/group-writable rejection boundaries. +- **Files**: `tests/service_macos.rs` and this roadmap. +- **Root cause / classification**: **测试夹具遗漏 / 跨平台盲区**. The macOS service tests also used platform-default `tempfile` roots. On Linux, positive cases were rejected at world-writable `/tmp`, while negative cases could pass for that earlier reason instead of proving their named boundary. +- **Prevention**: Cross-platform service tests now control the security of every ancestor. Positive and negative cases run from the same trusted base, so only each scenario's deliberate mutation can change the outcome. + +## 2026-08-15 — Keep systemd fixtures under a secure ancestor + +- **Change**: Moved the three successful Linux user-service manager fixtures from the system temporary directory to a runner-owned repository subtree. +- **Files**: `tests/service_linux.rs` and this roadmap. +- **Root cause / classification**: **测试夹具遗漏 / 跨平台盲区**. Linux creates `tempfile` directories directly below world-writable `/tmp`, so the production fd-walk correctly returned `UnsafeServicePath` before the fake systemd runner was exercised. macOS uses an owner-safe per-user temporary subtree and therefore did not expose the fixture error. +- **Prevention**: Positive user-service publication tests must use an explicitly owner-safe ancestor. Public temporary directories remain negative-test inputs, and the systemd suite stays in both Linux release runners. + +## 2026-08-15 — Test existing Domain state in the native layout + +- **Change**: Made the real CLI overwrite-protection test place its sentinel Root keystore in the target platform's native AgenNet state directory. +- **Files**: `tests/cli_bootstrap.rs` and this roadmap. +- **Root cause / classification**: **跨平台测试盲区**. The subprocess test unconditionally created the existing Domain marker in the macOS application-support directory. On Linux the production CLI correctly searched `.local/state/agenet`, saw no existing state, and continued to the TTY prompt instead of returning `DomainStateExists`. +- **Prevention**: Real CLI tests must build every expected filesystem artifact in the same target-specific location used by the child binary. The overwrite-protection scenario remains in both default and all-feature release gates. + +## 2026-08-15 — Keep identity purge fixtures under a secure ancestor + +- **Change**: Moved the successful identity-generation purge fixture from the system temporary directory to the runner-owned repository subtree. +- **Files**: `tests/bootstrap_identity_generation.rs` and this roadmap. +- **Root cause / classification**: **测试夹具遗漏 / 跨平台盲区**. The earlier secure-path sweep covered runtime and service fixtures but missed the dedicated identity-generation integration fixture. Linux places `tempfile` directly below world-writable `/tmp`, so the production fd-walk correctly rejected deletion; macOS places it below an owner-safe per-user temporary subtree. +- **Prevention**: Positive tests for destructive owner-only state operations must always use an explicitly owner-safe ancestor. World-writable temporary roots are reserved for negative rejection cases, and the full identity-generation suite remains in every release runner. + +## 2026-08-15 — Match subprocess fixtures to the runner platform + +- **Change**: Made the production pursuit subprocess fixture resolve its AgenNet config and state paths with the runner's real macOS or Linux layout. +- **Files**: `src/cli/mod.rs` and this roadmap. +- **Root cause / classification**: **跨平台测试盲区**. The parent test always provisioned the founding node under the macOS `Library/Application Support` layout, while a Linux child correctly interpreted the same isolated home as `.config` and `.local/state`. The live Requester was healthy, but the child could not discover its local control record and returned `LocalControlUnavailable`. +- **Prevention**: Any test that crosses a real process boundary must derive filesystem layout from the target platform exactly as production does. The real-binary pursuit test remains part of the four-platform all-feature release gate. + +## 2026-08-15 — Isolate host and secure-path tests in release CI + +- **Change**: Added the existing process-wide host runtime guard to the supervisor lifecycle test and moved all successful secure service/state-path and founding-host fixtures under the runner-owned repository instead of the intentionally unsafe world-writable system temporary directory. +- **Files**: `src/service/supervisor.rs`, `src/cli/node.rs`, `src/cli/lifecycle.rs`, `src/cli/evidence.rs`, `src/cli/mod.rs`, `src/runtime/host.rs`, `src/runtime/key_store.rs`, and this roadmap. +- **Root cause / classification**: **测试隔离盲区 / 跨平台盲区**. The supervisor test started the production-style founding Authority, Directory, and revocation listeners on the same fixed loopback ports as other guarded host tests. Separately, Linux `tempfile` roots live under world-writable `/tmp`, which the production service-path policy correctly rejects, while macOS test roots live under an owner-safe `/var/folders` subtree. Tests that expected successful secure publication accidentally depended on the macOS location. +- **Prevention**: Every test that starts the fixed-port founding host runtime must acquire `host_test_guard()`. Tests for successful secure path behavior must construct fixtures below an explicitly owner-safe ancestor; `/tmp` remains reserved for negative tests that prove rejection. Four-platform clean-runner CI remains mandatory before a release tag. + +## 2026-08-15 — Make inode checks lint-portable + +- **Change**: Centralized conversion of Unix `stat.st_dev` into the persisted `u64` device identifier with explicit Linux/macOS implementations. +- **Files**: `src/runtime/key_store.rs`, `src/bootstrap/identity_generation.rs`, `src/bootstrap/invitation.rs`, `src/bootstrap/managed_binary.rs`, and this roadmap. +- **Root cause / classification**: **技术盲区**. Linux defines `dev_t` as `u64`, while macOS uses a narrower type. Four security checks used a cast that is required on macOS but triggers `clippy::unnecessary_cast` on Linux under `-D warnings`; prior full Clippy evidence came only from macOS. +- **Prevention**: Security-critical OS metadata normalization now lives behind one target-specific function. Release candidates must pass the same pinned Clippy command on both Linux and macOS clean runners before tagging. + +## 2026-08-15 — Correct the first public release workflow + +- **Change**: Advanced the first corrected prerelease candidate from `v0.2.0-preview.1` to `v0.2.0-preview.2` and installed the exact `rustfmt` and `clippy` components used by the four-platform release matrix. +- **Files**: `.github/workflows/release.yml`, the package/release version sources, public guides, Skill, installer tests, site generated content, release notes, and this roadmap. +- **Root cause / classification**: **计划集成缺口 / 技术盲区**. The workflow installed Rust 1.97.1 with the `minimal` profile but immediately invoked two optional components that profile does not include. Local gates used an existing complete toolchain, so they did not reproduce the clean-runner bootstrap boundary. +- **Impact**: The public `v0.2.0-preview.1` tag failed before lint, test, archive, attestation, or publication; no GitHub Release or downloadable assets were created. The tag is retained and not rewritten. `preview.2` became the corrected candidate for the next release attempt. +- **Prevention**: CI bootstrap must explicitly install every invoked component and be verified on a clean hosted runner before a release tag. Public version parity tests and the aggregate preflight now travel with the corrected candidate. + +## 2026-08-15 — Build the bilingual AgenNet public site + +- **Change**: Replaced the GPT Sites starter with a Chinese-default, English-mirrored AgenNet landing page and documentation site, including a bounded WebGL2 particle field, fixed release installation, Agent bootstrap/Skill guidance, protocol architecture, honest status, and the Agent Society vision. +- **Files**: `site/`, `plan/03-v2-public-sites.md`, and this roadmap. +- **Decision reason**: The public surface must explain why AgenNet exists and make a new node installable without maintaining a second, drifting source of release truth. The build therefore regenerates its version, status, verified install block, and one-sentence Agent prompts from the canonical repository guides. +- **Visual boundary**: WebGL2 is progressive enhancement only. Server-rendered semantic content, the mineral-dark CSS atmosphere, mobile layout, keyboard landmarks, and reduced-motion fallback remain complete without graphics or motion. The site has no forms, authentication, telemetry, database, D1, R2, credential inputs, or live private node state. +- **Evidence**: All fourteen routes server-render in both languages; build, ESLint, zero-vulnerability npm audit, desktop/mobile browser inspection, no-overflow checks, reduced-motion source validation, and zero browser console warnings/errors pass. The install page uses the exact fixed `v0.2.0-preview.1` checksum-verifying block and rejects pipe-to-shell copy. +- **Boundary**: Publication retains **Developer Preview — physical acceptance pending**. The site and release do not claim the pending fresh WSL2 Agent run or two-physical-device overlay acceptance. + +## 2026-08-15 — Seal the preview release candidate + +- **Change**: Added the public `v0.2.0-preview.1` release note, a single aggregate release preflight, concise README installation entrypoint, and removal of a real local development path from the archive-bound README. +- **Files**: `docs/releases/v0.2.0-preview.1.md`, `scripts/preflight-preview-release.sh`, `tests/scripts/preview-release-preflight.sh`, `README.md`, Skill metadata, and this roadmap. +- **Decision reason**: Tag publication must consume one candidate whose package version, manifest, workflow, installer, guides, Skill, release copy, CLI surface, and security claims agree. Individual passing tasks are insufficient if their public boundaries drift when assembled. +- **Security boundary**: The preflight requires nonempty exact surfaces, immutable Action commits, executable safety scripts, no tracked private artifacts or high-confidence credentials, honest preview language, and no moving release paths. It runs release-manifest, installer, guide, archive, and Skill gates as one deterministic command. +- **Evidence**: `cargo fmt`, all-target/all-feature Clippy, default full tests, and all-feature full tests pass on the candidate in the listener-capable environment. The default sandbox's seven initial failures were exact loopback/service permission errors and all pass unchanged with the required host permissions. The aggregate preflight passes twice with identical output. +- **Boundary**: This seals source only; it does not create the tag or public prerelease. GPT Sites, fresh WSL2 Agent acceptance, and the existing physical two-device acceptance remain pending. + +## 2026-08-15 — Add the node bootstrap Skill + +- **Change**: Created the distributable `agenet-node-bootstrap` Agent Skill, a public-readiness helper, allowlisted public-status reference, deterministic Skill packager, and pressure-scenario contract. +- **Files**: `skills/agenet-node-bootstrap/`, `scripts/package-bootstrap-skill.sh`, `tests/scripts/skill-readiness.sh`, and `tests/skills/agenet-node-bootstrap-scenarios.md`. +- **Decision reason**: A one-sentence node request needs executable policy, not prose alone. The Skill must consistently choose the immutable release, distinguish WSL2 from native Windows, detect service/overlay ambiguity, preserve existing state, and stop before any enrollment secret reaches Agent context. +- **Security boundary**: The Agent never receives or echoes Invitations, passphrases, keys, tokens, private network coordinates, state files, or raw diagnostics. The helper has no arguments, emits only allowlisted public readiness facts, counts local overlay candidates without emitting their values, accepts only the exact user-installed preview binary, and treats ambiguity or status failure as terminal. +- **Evidence**: Official `skill-creator` validation passes. ShellCheck passes. Behavior tests cover macOS/WSL2, native Windows and unknown-architecture rejection, wrong binary version, missing service manager or overlay, multiple address/kind ambiguity, managed-state detection, public-status failure, sentinel non-disclosure, and byte-identical normalized Skill archives with exact members and metadata. +- **Honest acceptance boundary**: These are deterministic package and policy scenarios, not a fresh-model claim. The real Skill-discovery and instruction-following gate remains the user's later clean WSL2 Agent run after the public release and site exist. Physical two-device acceptance also remains pending. + +## 2026-08-15 — Publish canonical bootstrap guides + +- **Change**: Added Chinese and English human installation guides, Chinese and English Agent node-setup guides, and an offline synchronizer that validates the exact preview manifest and compiled CLI before copying canonical public content. +- **Files**: `docs/install/`, `docs/bootstrap/agent-node-setup.md`, `docs/bootstrap/agent-node-setup.en.md`, `src/bin/agenet-sync-public-guides.rs`, `tests/public_guides.rs`, and `Cargo.toml`. +- **Decision reason**: The public one-sentence Agent workflow needs one canonical source for version, immutable download location, supported platforms, real CLI arguments, and the human-only controlling-TTY boundary. Separate prose without executable parity tests would drift. +- **Security boundary**: The guides verify the fixed installer against the same release's `SHA256SUMS`, reject moving URLs and native Windows, never pipe a network response directly into a shell, and never ask an Agent to receive an Invitation, passphrase, key, token, private address, CIDR, or raw terminal output. Enrollment stops at a literal local-TTY handoff. +- **Evidence**: Tests require byte-identical POSIX installation blocks in all four guides, parse the block with `sh -n`, execute advertised argument shapes against the compiled Clap binary, reject secret/path/moving-release patterns, and prove two synchronizer runs reproduce the checked-in bytes. +- **Boundary**: The documents are canonical source files but are not public URLs yet. The Skill, release notes, GitHub tag, GPT Sites deployment, fresh WSL2 Agent acceptance, and physical two-device acceptance remain pending. + +## 2026-08-15 — Correct the installer doctor command + +- **Change**: Replaced the installer's advertised `agenet node doctor --json` follow-up with the real public CLI form `agenet node doctor --output json` and added a regression assertion that rejects the nonexistent flag. +- **Files**: `scripts/install.sh.template`, `tests/installer.rs`, and this roadmap. +- **Root cause / classification**: **计划集成缺口 / 规则违反**. The release copy was written from an earlier command sketch instead of being compared with the current Clap surface; shell installation tests only searched for successful installation text and therefore did not catch the invalid follow-up. +- **Prevention**: Every command printed by the installer, guide, Skill, or site must be extracted and checked against the compiled CLI help/argument parser before release sealing. Task 4 adds this parity gate for all public instructions. +- **Boundary**: The incorrect command was committed locally but never pushed, tagged, or published, so no external user consumed it. + +## 2026-08-15 — Generate the verified preview installer + +- **Change**: Added a manifest-rendered fixed-version POSIX installer, strict offline whole-release verification, checksum generation, four archive attestations, and a tag-only idempotent prerelease publication job. +- **Files**: `src/release/installer.rs`, `src/bin/agenet-render-installer.rs`, `scripts/install.sh.template`, `scripts/verify-release.sh`, installer Rust/shell tests, `.github/workflows/release.yml`, and `packaging/release.md`. +- **Decision reason**: A one-sentence Agent installation path is only trustworthy when target selection, URL, size, digest, archive layout, binary version, and final release asset set all derive from one immutable manifest and fail closed on divergence. +- **Security boundary**: The installer accepts no arguments or secrets, uses HTTPS-only bounded downloads, rejects native Windows and unsafe archives, installs only to `~/.local/bin`, preserves a different existing binary, cleans interrupted staging, and never enrolls or runs `node join`. The publish job alone receives scoped write and attestation permissions. +- **Evidence**: Rust rendering tests and shell scenarios cover four supported OS/architecture pairs, WSL-shaped Linux, same-version idempotency, different-binary preservation, download/hash/size corruption, unsafe links, unwritable or interrupted destinations, secret sentinel absence, and exact offline checksum-set validation. Official `actionlint` v1.7.12 and ShellCheck v0.11.0 pass after their downloaded archives are verified against upstream SHA-256 values. +- **Boundary**: No tag or GitHub prerelease is published by this step. Canonical guides, the bootstrap Skill, release notes, GPT Sites deployment, fresh WSL2 Agent acceptance, and physical two-device acceptance remain pending and will block the tag workflow until present. + +## 2026-08-15 — Build deterministic preview release archives + +- **Change**: Added exact native archives for macOS arm64/x86_64 and Linux arm64/x86_64, a fail-closed offline archive checker, the MIT license payload, packaging documentation, and a native GitHub Actions matrix pinned to immutable official Action commits. +- **Files**: `Cargo.toml`, `Cargo.lock`, `LICENSE`, `scripts/package-release.sh`, `scripts/check-release-archive.sh`, `tests/scripts/release-archive.sh`, `.github/workflows/release.yml`, `packaging/release.md`, release manifest tests, physical evidence fixture, and public version copy. +- **Decision reason**: Every downstream installer, Skill, guide, and site must consume the same byte-stable archives. Archive verification rejects traversal, links, extra members, wrong modes or ownership, unstable timestamps or ordering, source drift, and version/commit mismatch before extraction. +- **Post-mortem (计划集成缺口)**: The release plan named `v0.2.0-preview.1`, while Cargo and the physical evidence fixture still identified the binary as `0.2.0`. Leaving that split would have published preview filenames around a stable-looking binary. A package-version parity test now makes the mismatch impossible, and the evidence template requires the same preview version. +- **Boundary**: The workflow builds and uploads candidate archives only at this step. Checksums, attestations, installer, GitHub prerelease publication, GPT Sites, WSL2 acceptance, and physical two-device acceptance remain pending. + +## 2026-08-15 — Record the Agent Society and collective AGI vision + +- **Change**: Defined the durable narrative from AgenNet to an authorized Agent Network, an institution-bearing Agent Society, and a possible collective AGI. Recorded both horizontal coordination and vertical civilizational inheritance through an Agent Library, School, organizations, maintenance and recovery, public safety and justice, and quality-of-service transport. +- **Files**: `docs/vision/agent-society.md`, `README.md`, and this roadmap. +- **Decision reason**: AgenNet should not be presented as only another Agent RPC or orchestration framework. Its long-term research bet is that verifiable coordination can help heterogeneous intelligence and resources form useful system-level capabilities that no single Agent possesses. +- **Boundary**: Connectivity does not imply coordination, and coordination does not imply AGI. Every proposed institution is a provisional analogy and must earn protocol status through realistic comparative evidence. The v0.2 implementation remains a minimum Contract-and-Evidence coordination substrate. +- **Narrative rule**: Public material must separately label implemented evidence, active Agent Society research, and the long-term collective AGI hypothesis. It may be ambitious about the future but may not describe Library, School, companies, maintenance, public safety, transport markets, or physical multi-host acceptance as present capabilities. + +## 2026-08-15 — Define the immutable preview release manifest + +- **Change**: Added the strict `v0.2.0-preview.1` four-platform release manifest model, checked-in JSON Schema, and an offline deterministic manifest generator. +- **Files**: `src/release/`, `src/bin/agenet-release-manifest.rs`, `schemas/release-manifest-v1.schema.json`, `tests/release_manifest.rs`, `src/lib.rs`, and `Cargo.toml`. +- **Decision reason**: The installer, Agent Skill, GitHub Release, and GPT Sites must consume one exact source of truth instead of independently reconstructing target names, URLs, hashes, or version claims. +- **Security boundary**: Validation requires the exact target set, fixed GitHub release paths, safe filenames, lowercase full hashes, bounded nonzero sizes, exact attestation subjects, a full commit SHA, and a real UTC publication timestamp. Generation is offline and publishes the JSON atomically. +- **Evidence**: The focused suite covers strict deserialization, schema parity, deterministic byte output, actual archive hashing, replacement without temporary-file residue, impossible timestamps, traversal, incomplete targets, and URL/version mismatches. +- **Release boundary**: No archive, installer, tag, GitHub Release, Skill, or public site has been published by this task. Physical acceptance remains pending. + +## 2026-08-15 — Prioritize the preview release, bootstrap Skill, and GPT Sites + +- **Change**: Reordered the remaining Developer Preview work so a fixed GitHub Release, verified macOS/Linux/WSL2 installer, Agent bootstrap guide and Skill, and bilingual GPT Sites documentation publish before the physical two-device acceptance run. +- **Files**: `docs/superpowers/specs/2026-08-15-preview-release-skill-sites-design.md`; forthcoming amendments to installation and public-site plans. +- **Decision reason**: The next physical test will be performed from an Agent running inside WSL2 and must exercise the same public one-sentence installation path future users receive. Publishing immutable, manifest-driven surfaces first makes that test representative instead of relying on repository-local commands. +- **Security boundary**: The installer installs only a checksum-verified binary and never handles enrollment secrets. The Agent stops at every Invitation/passphrase/key boundary and delegates secret entry to the human's controlling TTY. Native Windows remains unsupported; WSL2 is the Windows-facing environment. +- **Release boundary**: The first tag is `v0.2.0-preview.1`, not `v0.2.0`; all public copy must retain **Developer Preview — physical acceptance pending** until the existing Task 14 gate passes. +- **Prevention**: Release artifacts, installer, Skill references, raw Agent guides, and Sites commands must be generated from one strict immutable release manifest so public surfaces cannot drift. +- **Implementation plans**: `plan/02-v2-installation-surfaces.md` supersedes the installation details in `02-v1`; `plan/03-v2-public-sites.md` replaces the prior Astro/GitHub Pages host with a Vinext GPT Sites project; `plan/04-v1-preview-release-orchestration.md` controls immutable tag publication, public deployment, and the fresh WSL2 Agent acceptance. + +## 2026-08-15 — Task 14 review: verify live peer identity + +- **Change**: Locked the pinned invitation journal during initial replay and made physical evidence collection prove both A and B are live through their exact configured self-mTLS peer endpoints. +- **Files**: Invitation journal/replay race test; HostRuntime peer health; evidence collector and negative tests; production A/B pursuit test; Task 5 amendment, v4 plan, README, and physical runbook. +- **Root cause / classification**: **技术盲区 / 安全边界遗漏**. A fresh store could decode the journal while another process held the journal lock and had written only part of a framed record. Device B evidence also inferred runtime health from service-manager and persisted metadata without a cryptographically bound live request. +- **Solution**: Initial replay acquires the same bounded exclusive flock on the already-open, inode-verified journal before reading. HostRuntime injects one process generation into every production peer router; mTLS `/healthz` exposes only public Node/process/readiness/revocation fields and marks stale/revoked state non-current while preserving read-only audit visibility. The collector loads the validated local cert/key/CA bundle, disables proxy and redirects, expects the exact self Node ID, probes the exact configured endpoint, bounds and strictly parses the response, and matches it to credential and service metadata. Device A retains the additional loopback Requester probe. +- **Prevention**: Every framed journal reader must participate in the writer's inode lock domain, including first open. Every physical liveness claim requires an active authenticated request tied to the same durable process generation; platform status and ready metadata remain independent supporting observations, never substitutes. +- **Boundary**: All evidence is local loopback/mTLS preflight. No physical-device result, milestone, or Task 14 completion is claimed. + +## 2026-08-15 — Task 14 review: preserve lock and runtime liveness integrity + +- **Change**: Pinned the invitation operation-lock inode per store, added an exclusive lock on the pinned journal inode, and made evidence collection verify a live platform service plus exact loopback Requester health identity. +- **Files**: Invitation store/journal and replacement/race tests; local-control health, collector, evidence schema/tests; pursuit dotenv allowlist; Task 14 plan/runbook and README. +- **Root cause / classification**: **技术盲区 / 安全边界遗漏**. Reopening the operation-lock pathname on every operation allowed a regular `0600` replacement to create two advisory-lock domains. The collector trusted ready and service metadata without proving a process was alive, and the pursuit parser temporarily retained unrelated dotenv entries. +- **Solution**: Fix lock order to store Mutex → bounded pinned operation flock → bounded pinned journal flock; compare path and opened lock identity after acquisition and before reload/append; keep reload through sync inside the journal lock. Require platform service `Running` on A/B, and on A require a no-proxy/no-redirect bounded `/healthz` response matching ready and service Node/process IDs. Retain only the three model aliases and redact key diagnostics. +- **Prevention**: Every pathname lock requires a normal-file replacement test, every durable-ready claim requires an active liveness observation tied to one process generation, and config allowlists must filter during parsing rather than after collection. +- **Boundary**: These are local preflight integrity tests. They do not constitute the pending physical two-device private-overlay evidence or Task 14 completion. + +## 2026-08-15 — Task 14 preflight: refresh live invitation state + +- **Change**: Added one owner-only cross-process invitation operation lock and bounded journal refresh/replay before invitation mutations, so a long-lived Authority sees invitations created later by the real CLI without restart. +- **Files**: invitation journal/store, cross-instance/race/crash/replacement tests, Task 5 report amendment, Task 14 runbook, and real-binary preflight. +- **Root cause / classification**: **计划集成缺口 / 技术盲区**. Task 5 proved thread races through one in-memory store, while production uses a CLI process and a long-lived Authority process. Each held a stale projection and independent append descriptor; a post-start CLI invitation was durable but invisible to Authority reservation. +- **Solution**: Serialize Mutex→`flock` in one order, anchor the lock to an owner-only directory/file, verify the journal pathname still names the opened inode, full-replay bounded checksummed events under the lock, and retain persistence poison semantics. No invitation/credential/journal format changed. +- **Prevention**: Every durable component consumed by both CLI and service must include a real multi-process create-after-service-start acceptance test; thread-only races cannot close a process coordination requirement. +- **Boundary**: The lock coordinates AgenNet processes for one user. It does not defend against a malicious same-euid process that ignores advisory locks, and it is not distributed consensus. + +## 2026-08-15 — Task 14 preflight: authorize the verifier Manifest + +- **Change**: Expanded the production Provider invitation ceiling from only `source.metrics.v1` to the exact pair `source.metrics.v1` and `source.metrics.verify.v1`; split physical evidence into signed credential roles, active runtime roles, and local Requester enablement. +- **Files**: `src/cli/invite.rs`, Task 14 evidence schema/verifier/collector/tests, and the physical acceptance runbook. +- **Root cause / classification**: **计划集成缺口 / 技术盲区**. Provider credentials sign Requester, Executor, and Verifier roles, while runtime intentionally activates only Executor and Verifier without a local-control token. The invitation ceiling authorized only the Executor kind, so the real HostRuntime would reject its own signed Verifier Manifest during startup. The first evidence draft also collapsed signed authorization and active runtime exposure into one ambiguous role list. +- **Solution**: Keep credential and invitation formats unchanged, authorize exactly the two published read-only Provider kinds, and require evidence to show A signed/active Directory+Requester with local Requester enabled, versus B signed Requester+Executor+Verifier but active Executor+Verifier with local Requester disabled. +- **Prevention**: Every multi-role bootstrap profile must test that each production Manifest kind is inside the issued ceiling, while evidence must distinguish signed authorization from routes actually activated by local secret/config prerequisites. +- **Boundary**: The additional kind remains the fixed independent source-metrics verifier; this does not authorize arbitrary capabilities or enable B's Requester surface. + +## 2026-08-15 — Task 14 preflight: expose the production pursuit path + +- **Change**: Added a focused v4 plan amendment before implementing physical-evidence tooling and the production two-device run. +- **Files**: `plan/01-v4-multi-host-node-bootstrap.md`, Task 14 evidence schema/verifier, and the forthcoming founding Requester/local-control path. +- **Root cause / classification**: **计划集成缺口 / 误解需求**. Task 12 proved the complete pursuit only through the loopback demo. Production `domain init` issued a Directory-only credential, `HostRuntime` returned after constructing Directory routes, and the public CLI had no pursuit command or secure model/control configuration. Consequently the approved physical topology A=Directory+Requester and B=Executor+Verifier was not executable without the demo harness. +- **Solution**: Keep the demo least-privilege and add an explicit production-only founding Directory+Requester credential, owner-only local-control token, loopback control listener, and source-metrics pursuit CLI whose only remote knowledge is the signed Directory seed. Finish strict redacted evidence tooling independently, but do not record a milestone until the real physical path passes. +- **Prevention**: Every future acceptance plan must map each operator step to a public production command and its durable/runtime artifact before implementation begins. Simulated and demo-only entrypoints are never presumed to imply an operator-facing production path. +- **Boundary**: This revision does not relax private-overlay, mTLS, signed Manifest, LLM, revocation, or physical-device requirements. Local tests remain nonphysical evidence. + +## 2026-08-15 — Task 13 review: serialize identity generation changes + +- **Change**: Restored default-feature generation security tests and added two-level advisory locking for lifecycle operations and active identity generations. +- **Files**: identity generation/key-store/path persistence, startup/doctor/lifecycle loaders, generation concurrency tests, README/design, and v3 plan amendment. +- **Root cause / classification**: **规则违反 / 技术盲区**. Fault-only test imports were feature-gated at the library boundary but imported unconditionally by a default test target, breaking `cargo test --all-targets`. Generation validation and fd-relative cleanup also lacked coordination with concurrent pointer publication, so a retired generation could become active after cleanup's initial check. +- **Solution**: Core production roundtrip/tamper/purge tests now compile under default features, while only narrow fault seams remain feature-gated. A full-workflow operation lock serializes renew/purge, and shared/exclusive `flock` on the pinned identity-generation directory serializes load against publish/rollback/cleanup/purge. Cleanup re-reads active under exclusive ownership and retains the lock through unlink and sync. +- **Prevention**: Every feature-gated test import must be exercised by both default and all-feature compile gates. Security cleanup designs must identify the concurrent selector writer and prove reader/writer blocking with real OS locks, including cross-process and abrupt-exit recovery. +- **Boundary**: Advisory locks coordinate AgenNet processes but do not defend against a malicious same-euid process that ignores them. Physical overlay and Linux native service adoption remain Task 14 gaps. + +## 2026-08-15 — Task 13 review: pin lifecycle recovery artifacts + +- **Change**: Hardened identity generation load/cleanup and made Directory departure recovery exact and durable across offline `Left` and receipt-write crashes. +- **Files**: identity-generation/key-store persistence, lifecycle and peer client, Directory departure policy, doctor/lifecycle/filesystem tests, README/design, and the v3 plan amendment. +- **Root cause / classification**: **技术盲区**. The first generation loader did not hash-check the Node signing key and used separate pathname reads, while retired cleanup re-opened a generation after validation. Leave also treated `Left` as complete without retaining the exact signed Directory request/receipt relationship. +- **Solution**: A pinned owner-only parent/generation descriptor now supplies the manifest and every bounded material read, exact manifest/directory sets are enforced, and cleanup compares device/inode before fd-relative directory removal. Leave persists one exact signed request before networking, reuses it after `Left`, durably stores the exact Directory-signed receipt, and reconciles pending-plus-receipt state. +- **Prevention**: Security bundles require per-material tamper/missing/symlink tests plus replacement races and sync-fault seams. Recoverable network effects require an exact persisted request, signed response binding, publication order, and crash-state matrix before CLI completion semantics are approved. +- **Boundary**: These tests use owner-controlled temporary filesystems and loopback protocol handlers. They do not establish physical-overlay behavior or Linux native service-manager parity; Task 14 remains deferred. + +## 2026-08-15 — Task 13 lifecycle, renewal generations, and diagnostics + +- **Change**: Added mTLS credential renewal, Root-authorized revocation, signed recoverable leave, conservative uninstall/purge, trusted managed-binary metadata, and deterministic read-only doctor checks. +- **Files**: lifecycle protocol/transport/runtime/CLI modules, identity-generation and managed-binary persistence, Directory departure handling, revocation Authority state, service reconciliation, tests, README/design, and v2/v3 plan amendments. +- **Decision reason**: Lifecycle operations must preserve the Task 12 live authorization boundary while remaining recoverable across service-manager, network, and filesystem uncertainty. Enrollment bearer authentication cannot prove current mTLS/private-key possession, and arbitrary executable paths cannot be trusted for uninstall. +- **Post-mortem (技术盲区 / 计划集成缺口)**: The initial Task 13 renewal implementation atomically replaced each credential/TLS file but did not make the set atomic. A crash could therefore leave a mixture of old and new valid files. The plan also omitted the exact binary producer metadata required for safe deletion. +- **Solution**: Renewal now writes a complete hash-manifested UUID generation, syncs it, and switches one owner-only active pointer. Startup with a pointer loads exactly that generation. The real user service must restart and publish readiness before inactive generation cleanup. Managed binary deletion requires an approved per-user root and exact owner/mode/device/inode/size/hash/version match; every mismatch retains it with a stable warning. +- **Prevention**: Every future multi-file security bundle must name its commit record, crash states, adoption observable, rollback rule, and retired-resource selector before implementation. Destructive commands require a producer-owned provenance record and fd-relative exact-object deletion tests. +- **Boundary**: Loopback mTLS and filesystem-backed tests do not establish physical overlay reachability. Native adoption evidence is platform/session-specific, Linux native service behavior remains an honest gap, and Task 14 is not started. + +## 2026-08-15 — Task 12 review: bind Contract capability to Provider role + +- **Change**: Added one protocol-owned capability registry for exact Capability ID, versioned kind, and required Provider role; reused it in bilateral Contract verification, Recorder Event replay, Provider dispatch, Directory registration, Requester routing, Artifact reads, and manifest construction. +- **Files**: protocol capability/Contract modules, runtime Provider/Recorder/Directory/Requester/Artifact/Host modules, peer handlers, demo provisioning, focused protocol/HTTP/multiprocess tests, design, and Task 12 amendment. +- **Root cause / classification**: **技术盲区**. Contract verification hard-coded every Provider as `Executor`, while several runtime and transport layers independently mapped strings. Verifier-only credentials were rejected, Executor-capable credentials could mask the mismatch, and least-privilege Event/Artifact envelopes could not traverse the full path. +- **Solution**: Unknown IDs and ID/kind cross-pairs fail closed. Contract verification derives the exact role and checks the exact capability ceiling plus Grant/Contract/Artifact binding. Provider Events and Artifact reads derive role from the already authorized stored Contract. The demo now provisions Directory-only, Requester-only, Executor-only, and Verifier-only credentials with exact ceilings. +- **Prevention**: Every future Capability must add one protocol registry entry and negative ID/kind/role/ceiling tests before any runtime handler. No transport or runtime layer may infer authorization from profile names or duplicate string matches. +- **Product boundary**: Enrollment `Provider` remains a policy profile whose issued credential may explicitly enable multiple permitted roles; effective execution is still limited by signed roles and the invitation-derived capability ceiling. The demo deliberately uses least privilege and does not rely on that broader product choice. + +## 2026-08-15 — Task 12 review: live authorization at effect boundaries + +- **Change**: Replaced HostRuntime's startup-frozen Recorder and Artifact clocks with the same injected live clock used by its identity, added current revocation checks for both Contract participants and each Event issuer, and required Provider, Requester, and PeerClient to revalidate local authority immediately before effects. +- **Files**: `src/protocol/sealed_contract.rs`, runtime Recorder/Artifact/Provider/Requester/Host modules, peer client, node harness wiring, focused host-runtime tests, design, and the Task 12 amendment. +- **Root cause / classification**: **技术盲区**. The first Task 12 implementation correctly used a live clock at HTTP envelope admission but converted it to a fixed startup timestamp when constructing Recorder and Artifact services. A detached Provider task could therefore continue after credential expiry or a new revocation snapshot. +- **Solution**: Contract registration, query, and Event append now verify signed participants under the Recorder mutation lock using the current clock and latest cache; Artifact authorization and the final byte-read boundary do the same while tracking only successful reads. Detached execution terminates without writing `Failed` when local policy becomes invalid. Peer send counters advance only after local live-policy validation. +- **Prevention**: Security review must trace every asynchronous effect from HTTP admission through delayed work and its final durable/network/read boundary. Tests advance a shared clock and replace the live revocation snapshot after startup, then compare exact journal bytes and read/request counters rather than mirroring policy with test-only sets. +- **Consistency boundary**: Policy recheck and journal mutation are serialized within one process and one cache view. Distributed revocation propagation is not atomic with an already-running operation; a stale or revoked local cache fails closed at each subsequent effect boundary. + +## 2026-08-15 — Task 12 host runtime, exact Directory identity, and mTLS loopback flow + +- **Change**: Attached the validated v0.2 host runtime to `node service-run`, migrated Directory seeds and enrollment artifacts to exact endpoint/NodeId pairs, added request-time clock/revocation enforcement, role/ceiling-safe Provider dispatch, readiness lifecycle, and an HTTPS/mTLS four-process loopback flow. +- **Files**: bootstrap invitation/enrollment/config migration, runtime identity/clock/host/provider/requester/recorder/artifact modules, peer TLS/Directory/revocation transports, node/demo/supervisor CLI, focused tests, README/design/v2 plan, and ignored Task 12 evidence. +- **Decision reason**: A URL did not authenticate a Directory, BootstrapProfile was incorrectly positioned to influence runtime roles, a fixed startup timestamp could keep expired/revoked identities effective, and Provider registration occurred before transport readiness. Exact signed identities, verified role sets, a live clock, and health-before-registration are required security boundaries. +- **Post-mortem (误解需求)**: The initial runtime model assumed one profile meant one role and kept a fixed validation timestamp. Prevention: every public effect derives authority from `VerifiedNodeClaims`, maps capability kind to a signed role and ceiling, and rechecks credential/revocation policy at request time. +- **Post-mortem (技术盲区)**: The first seed format persisted only a URL, so TLS could not bind the intended Directory NodeId without guessing. Prevention: version all invitation/enrollment/config consumers together and require `DirectorySeed { endpoint, node_id }`, rejecting duplicates and cross-pairs. +- **Evidence boundary**: The demo is `loopback_harness`; it uses mTLS and probes loopback aliases but falls back to distinct ports where aliases are unavailable. This is not physical multi-host, sandbox, failover, or Internet-scale evidence. Task 14 still owns physical-device acceptance. + +## 2026-08-15 03:26 CST + +- **Change**: Corrected Task 11 activation compensation and service-artifact pathname trust after review. Rollback now requires observed stopped process plus exact artifact absence; service publication and deletion walk and pin every absolute ancestor from `/`. +- **Files**: `src/cli/node.rs`, `src/runtime/key_store.rs`, macOS/Linux service managers and tests, the focused v2 plan, this roadmap, and ignored Task 11 review evidence/report. +- **Root cause / classification**: **技术盲区 / 计划集成缺口**. The first implementation treated best-effort cleanup calls as proof of cleanup and validated only the final service parent before mutation. A failed stop/remove/status could therefore be followed by a false `CredentialIssued` rollback, while an unsafe or replaced ancestor remained outside the pinned-object boundary. +- **Solution**: Call platform uninstall once and propagate macOS `bootout` or Linux `disable --now` failure. Keep `ServicePrepared` and return sanitized `ServiceRollbackIncomplete` unless process state, manager installation state, and exact artifact absence all agree. Resolve every path component using `openat` with `O_DIRECTORY|O_NOFOLLOW`, accept only root/current-euid ownership with no group/world write, create missing user-owned components as `0700`, and perform atomic publish/delete through the same pinned final-parent descriptor. +- **Evidence**: A filesystem-backed lifecycle fake asserts artifact inode and durable `ServicePrepared` before activation, then covers stop failure, removal failure, command timeout, post-delete status uncertainty, credential retention, and retry reconciliation. Path tests cover writable/foreign-owner policy, `/tmp`, ancestor symlinks, ancestor replacement after validation, final-file binding, and temporary cleanup. Platform tests prove failed bootout/disable retains the artifact. +- **Prevention**: Model compensation as a fact table over journal, process, and filesystem observables; never equate a method invocation with a completed side effect. Every security-sensitive pathname operation must document and test its complete ancestor trust chain and must mutate through the same descriptor walk that performed validation. +- **Boundary**: Post-delete durability uncertainty intentionally leaves `ServicePrepared` even when the current pathname is absent. The next start republishes and reconciles; no cleanup error deletes credentials or claims runtime readiness. + +## 2026-08-15 02:45 CST + +- **Change**: Added recoverable macOS LaunchAgent and Linux systemd user-service management, explicit `node start|stop|status`, and a bootstrap-only service supervisor. +- **Files**: `src/service/`, `src/cli/node.rs`, bootstrap CLI follow-ups, service/CLI tests and probe fixture, README, design/spec, focused v2 plan, this roadmap, and ignored Task 11 evidence/report. +- **Decision reason**: Task 11 required a real activatable service before Task 12 owns network-runtime integration, but the only existing internal node entrypoint was the loopback demo harness. Treating it as the host runtime would have selected the wrong roles and transport; installing a process that immediately exits would have fabricated service readiness. +- **Solution**: Add an internal `node service-run --config ` supervisor that validates the full persisted startup bundle and schema-2 journal, holds the bootstrap lock, handles termination, and opens no listener. Publish the service artifact before `ServicePrepared`, activate only after that transition is durable, and compensate activation failure by uninstalling the artifact and rolling back to `CredentialIssued` while retaining credentials. Report process state separately from the always-false pre-Task-12 `runtime_ready` value. +- **Platform boundary**: LaunchAgent uses GUI-user `launchctl bootstrap/bootout`, `RunAtLoad`, bounded failed-exit keepalive, and state-root logs. systemd uses `--user`, `daemon-reload`, `enable --now`, bounded on-failure restart, and user-unit-compatible hardening. Both start only after login; no sudo, system unit, linger, shell, environment file, or embedded secret is used. +- **Verification**: Pure renderers and injected runners cover spaces, escaping, exact argv, idempotent start/stop, unavailable sessions, activation rollback/retry, symlink rejection, and honest status. A uniquely labelled macOS LaunchAgent ran a compiled non-shell probe and was removed through the tested uninstall path with no residual matching definition. +- **Root cause / classification**: Plan integration gap, classified as **误解任务边界**. The plan separated service management and runtime wiring without naming an executable pre-runtime service contract. +- **Prevention**: Every future service task must identify the exact executable entrypoint, its durable readiness predicate, and the next task's handoff before implementation. Phase names, process liveness, network readiness, registration, and health must remain separate observables. +- **Test post-mortem / technical blind spot**: One parallel full-suite run observed `StateLocked` when the rollback/retry test reopened the journal immediately after an assertion constructed a temporary lock-bearing store. The same scenario passed alone. The test now reads phase through a helper that drops the store before returning, and the exact scenario passed five consecutive runs before the full gate was repeated. Tests must never embed lock-owning resources in assertion expressions immediately before a reacquisition step. +- **Boundary**: Task 11 proves login-scoped supervision only. Task 12 must attach the live v0.2 runtime to the same entrypoint before `Registered`, `Healthy`, routing, or multi-host reachability can be claimed. + +## 2026-08-15 02:15 CST + +- **Change**: Removed the unchecked Directory capability-registration API and replaced the `--version` process assertion with a bootstrap-specific JSON output contract exercised through the real binary. +- **Files**: `src/protocol/envelope.rs`, `src/runtime/directory.rs`, `src/transport/directory.rs`, `src/cli/mod.rs`, `tests/http_directory.rs`, `tests/cli_bootstrap.rs`, `Cargo.toml`, this roadmap, and ignored Task 10 review evidence/report. +- **Root cause**: Security API and test-observable gaps — the HTTP handler enforced the signed capability ceiling, but a public legacy registry method still accepted a raw `NodeId` and inserted a Manifest without verified claims. The subprocess “success” test only executed `--version`, so it never observed a bootstrap phase or advertised next command. +- **Solution**: Delete the unused legacy mutation rather than preserve unsafe compatibility. The only registry mutation now consumes an `OpenedEnvelope` whose private fields can only be produced by exact envelope/credential verification; HTTP opens once, applies revocation policy to those claims, then consumes the same proof. Add a `cli-test-fixture` feature that lets the real binary read a nonsecret, owner-only, replay-validated bootstrap journal and emit the production JSON operator contract; the process test requires exact `CredentialIssued`, checks the follow-up with Clap, and scans stdout/stderr for a sentinel. +- **Prevention**: Every authorization-sensitive collection gets one mutation gate whose input type proves verification; raw identifiers and caller-constructible claims are not compatibility APIs. Process tests must assert domain behavior, not generic executable liveness. +- **Boundary**: The fixture feature injects no passphrase, invitation, credential, or success phase. It only reports an already durable schema-2 `CredentialIssued` journal and is disabled by default. The runner still denies `/dev/tty` even after PTY/session setup, so interactive PTY success remains unclaimed and production TTY behavior is unchanged. +- **Post-mortem**: Classified as a security boundary omission. Compatibility review must begin with a consumer search; an unused API that bypasses a new authorization invariant must be deleted. + +## 2026-08-15 06:20 CST + +- **Change**: Corrected Task 10 review findings in bootstrap phase truthfulness, durable capability authorization, strict Authority CA reload, and fallible CLI output. +- **Files**: Bootstrap journal/state and tests, Node Credential protocol and constructors, enrollment and Directory registration, CLI success results/output, PKI reload, README, design, and plan. +- **Root cause**: Requirement integration gap — Task 10 reused Task 9's provisional service-before-enrollment order and advertised a Task 11 command, while invitation capability authorization stopped at the bearer instead of surviving in the Authority-signed credential. PKI reload and stdout handling also accepted broader inputs/failure behavior than provisioning emits. +- **Solution**: Bump the bootstrap journal to schema 2 with `ReadyForEnrollment → CredentialIssued → ServicePrepared`; stop Task 10 at `CredentialIssued`; advertise only Clap-accepted commands. Bump Node Credential format/signing domain to v0.3, sign and persist the exact capability ceiling, compare it during handoff validation, and enforce it at Directory registration. Require one canonical CA PEM with the exact generated CA constraints/KU/self-signature/key binding, and use locked fallible writers for CLI output. +- **Prevention**: Every durable phase must correspond to an observed artifact, and every authenticated authorization input must be traced through issuance, persistence, restart, and its final enforcement point. Success output and reload parsers require executable negative tests, not only construction tests. +- **Boundary**: Service installation/start remains Task 11; Task 12 must consume verified credential claims without widening capability scope, and Task 14 setup automation must preserve the same ceiling. +- **Post-mortem**: Classified as technical blind spot. Future provisional schema reviews must include an end-to-end artifact/authorization ledger before exposing a CLI. + +## 2026-08-15 03:40 CST + +- **Change**: Added the Task 10 bootstrap CLI and corrected the provisional invitation/enrollment wire boundary before exposing it to operators. +- **Files**: `src/cli/`, `src/main.rs`, bootstrap invitation/enrollment and persistence paths, protocol/CLI tests, design, plan, and README. +- **Root cause**: Security design gap — invitation v2 did not carry Domain-owned overlay policy and enrollment v0.2 did not sign a joining node bind IP. Its client-only certificate could not pass Task 9's exact-IP peer startup validation. +- **Solution**: Emit invitation handoff/journal v3 with overlay kind and allowed CIDRs; sign `requested_bind_ip` in enrollment v0.3; validate it before reservation; issue and validate a dual-use certificate with one exact IP SAN. Old formats fail closed. Added TTY-only Domain/invitation/join commands, durable pending-operation recovery, startup-bundle validation, and stable redacted output. +- **Prevention**: Work backward from the complete daemon startup validator. Every transport-identity input must be explicitly authorized, signed, persisted, and tested before a bearer can be consumed. +- **Boundary**: Task 10 stops at `CredentialIssued`. Service start, registration, doctor, live route clocks, and physical two-device proof remain deferred. + +## 2026-08-15 00:28 CST + +- **Change**: Closed Task 9 review-round-two leakage of failed private-material temporary publications. +- **Files**: `src/runtime/key_store.rs`, `README.md`, this roadmap, and ignored Task 9 report/evidence. +- **Root cause**: Regression in the round-one fd-relative refactor — the temporary file was created securely, but metadata, write, flush, and file-sync used `?` before the later explicit cleanup block. Those early returns could retain partially or fully written secret-bearing `0600` temporary names and accumulate them across repeated failures. +- **Solution**: Arm an fd-relative `PendingTemp` guard immediately after successful `openat`. Every pre-publication return unlinks through the pinned parent fd; successful rename or link-plus-unlink publication explicitly disarms the guard before directory sync. Post-publish sync uncertainty therefore retains the final file, while non-replacing publication keeps its prior no-overwrite semantics. +- **Verification**: A real fault seam covers failure after a partial write, after flush, and after file sync. With cleanup deliberately disabled, the test observes the secret temporary filename; with the guard active, the original final and attacker boundary remain unchanged and the directory contains no temporary entry. +- **Journal boundary**: A new journal's final owner-only name is created before its header is written and synced. Header write/file-sync/parent-sync uncertainty retains the file; restart accepts only a complete valid header and otherwise fails closed. Automatic deletion is forbidden because a complete header may already be durable despite a reported sync error. Task 10 must expose explicit repair/reconciliation rather than guessing. +- **Post-mortem**: Cleanup ownership must begin at resource creation, not after the first fallible operation. Every future secret-bearing temporary resource needs a drop/RAII failure path tested after partial write and after durable file sync. + +## 2026-08-15 00:15 CST + +- **Change**: Closed Task 9 review-round-one persistence races without changing the provisional schema or entering Task 10. +- **Files**: `src/runtime/key_store.rs`, `src/bootstrap/paths.rs`, `src/bootstrap/state.rs`, adjacent compatibility fixtures, focused bootstrap tests, `README.md`, this roadmap, and ignored Task 9 report/evidence. +- **Root cause**: Security-hardening implementation gap — atomic publication validated a parent pathname but then re-resolved it for temporary creation, publication, and directory sync. The bootstrap journal separately replayed one path-opened file and appended through a later open. Replay also omitted the live `operation_id` validator, and live append omitted replay's total-record/total-byte limits. +- **Solution**: Pin publication to a verified parent directory descriptor and perform temporary creation, rename/link, cleanup, and parent sync with fd-relative syscalls. Bootstrap lock creation, journal create/open, header sync, replay, and append now share one verified `0700` directory fd and one owner-only journal fd. Replay validates operation IDs identically; append preflights exact next record count and serialized journal length before any write or memory transition. +- **Compatibility decision**: Task 9 managed config/credential/TLS paths use the exact-`0700`, reject-all-symlink-components writer. The established public signing-key/keystore writer retains its owned, non-group/world-writable parent contract (including macOS `/var` platform aliases) while still pinning the opened parent inode and using fd-relative publication. This avoids silently broadening Task 9 trust while preserving reviewed legacy consumers. +- **Recovery decision**: Resource-limit rejection is certain: it appends no bytes, advances no in-memory phase, and does not poison the store; restart replays the prior state. Write/flush/sync uncertainty still poisons until restart. A deterministic directory-replacement seam proves lock, header, and append stay in the original directory and leave the attacker target unchanged. +- **Multi-file boundary**: Startup credential/TLS files are separate atomic publications, not one transaction. A missing partial file makes `load_startup_bundle` fail closed; callers must publish and validate the full set before `CredentialIssued`. Task 10 owns command-level retry/reconciliation and must not delete credentials based on an incomplete set. +- **Post-mortem**: Filesystem validation and mutation must consume one pinned object; live admission and replay must share validators and bounds. Future persistence changes require deterministic replacement/fault seams plus tests that compare pre-failure memory, bytes on disk, and restart projection. + +## 2026-08-14 23:24 CST + +- **Change**: Added Task 9's provisional versioned node configuration, deterministic macOS/Linux user paths, owner-only startup material loader, cross-process state-root lock, and crash-recoverable bootstrap phase journal. +- **Files**: `src/bootstrap/config.rs`, `src/bootstrap/paths.rs`, `src/bootstrap/state.rs`, hardened key/TLS helpers, focused bootstrap tests, `README.md`, this roadmap, and ignored Task 9 report/evidence. +- **Configuration boundary**: `agenet.node-config` schema 1 denies unknown fields, is bounded to 64 KiB, rejects empty/duplicate/excess Directory seeds, and permits only exact IP-literal root endpoints inside the selected boundary. Private-overlay endpoints require HTTPS; plaintext compatibility is explicit loopback policy only. DNS, userinfo, query, fragment, and non-root paths fail before startup. +- **Filesystem boundary**: Current-host roots come only from `directories`; deterministic tests inject explicit platform/root values without global environment mutation. Managed directories are non-symlink, current-euid `0700`; credentials, Ed25519/TLS keys, certificates, CA, config, journal, and lock are separate owner-only regular files. Reads use `O_NOFOLLOW|O_NONBLOCK`, bounded length, and pre/post-open identity checks. The validated startup bundle re-verifies Root/domain/role/profile/time, Ed25519 public-key binding, Authority CA chain, TLS key, NodeId, exact IP SAN, and certificate time before returning material to a later runtime wiring task. +- **Recovery decision (superseded by the 2026-08-15 06:20 correction)**: Task 9 originally placed service preparation before enrollment. Journal schema 2 now uses the artifact-honest order and compensation documented above; schema 1 is rejected rather than reinterpreted. +- **Durability and writer policy**: One owner-only nonblocking `flock` per state root is held for the store lifetime. Append validates before writing, flushes and syncs before advancing memory, poisons mutation after uncertain persistence, and requires restart reconciliation. Atomic replacement uses same-directory `0600` temporary files, file sync, publish, and parent-directory sync; a post-publish sync error is reported as uncertain and tests prove the published final is visible after restart. +- **Provisional boundary**: These file names, schema, and transitions are explicit v1 choices, not permanent invariants. Task 10 may consume them but must not silently reinterpret unknown versions. Task 12 still owns full runtime/service wiring and live-clock enforcement. The previously recorded Task 5 `encode_handoff` growable serializer remains a must-fix before Task 10. +- **Post-mortem**: Security-hardening integration gap — the first complete gate showed the existing macOS multiprocess demo timing out before `ready.json`. The hardened signing-key reader correctly rejected every symlink component, but the demo passed TempDir's `/var/...` spelling through the system `/var → /private/var` symlink to children. Provisioning now canonicalizes the just-created trusted run root before deriving or passing any child path; the Task 9 managed-root symlink policy remains strict. A second gate exposed that applying the same component policy to the pre-existing public signing-key API broke its established `/var/...` consumer contract. The public API now retains bounded final-file owner/mode/`O_NOFOLLOW` compatibility, while Task 9 startup uses a separate crate-private component-hardened reader. Future filesystem hardening must test both hostile user symlinks, platform path aliases, and existing public consumers before broadening a helper's policy. + +## 2026-08-14 23:38 CST + +- **Change**: Corrected the Task 8 outbound response-verification order so cryptographic validation always precedes TLS NodeId binding classification. +- **Files**: `src/transport/client.rs`, `tests/http_mtls.rs`, `README.md`, this roadmap, and ignored Task 8 report/evidence. +- **Root cause**: Security validation-order omission — `PeerClient::post_signed` compared the unverified JSON `issuer_id` with the TLS peer before opening the envelope, so a response with both a wrong issuer and invalid signature returned `TlsIdentityMismatch` without exercising credential or signature verification. +- **Solution**: Open the response envelope exactly once and map every credential, role, domain, expiry, or signature failure to `InvalidSignedResponse`; only then compare the now-verified envelope issuer with the expected TLS peer and return `TlsIdentityMismatch` for an otherwise valid response from the wrong node. +- **Post-mortem**: Treat every parsed identity claim as attacker-controlled until its enclosing cryptographic object has verified. Error classification and identity binding must consume verified claims, never pre-validation JSON fields. +- **Verification**: A real mTLS fixture now flips one stable response-signature bit while also using a different signed issuer and requires `InvalidSignedResponse`; the existing valid wrong-issuer fixture still requires `TlsIdentityMismatch`. + +## 2026-08-14 23:10 CST + +- **Change**: Closed the Task 8 review gaps by making every public `/v0` peer route fail closed unless the connection carries either Rustls-derived certificate identity or loopback `ConnectInfo`, enforcing a single exact server SAN in both client and server directions, and requiring strict one-block PKCS#8 server keys. +- **Files**: `src/transport/tls.rs`, `src/node.rs`, peer HTTP tests, `tests/http_mtls.rs`, this roadmap, and the ignored Task 8 report/evidence. +- **Root cause**: Security boundary omission — the first middleware treated absence of the private TLS extension as loopback compatibility without proving the connection was loopback; WebPKI accepted a matching target IP even when additional SAN identities were present; and the server key parser accepted the first supported key while ignoring additional PEM blocks. +- **Solution**: Plain HTTP compatibility now depends on connection-layer loopback metadata and the root node server supplies it with Axum `ConnectInfo`; missing or non-loopback metadata is rejected before request parsing and registry mutation. Both local and remote leaves must have exactly one SAN entry and it must equal the expected IP. Certificate/CA/key PEM inputs use strict framing and type checks; a server key is exactly one PKCS#8 block. `PeerClient` tests now prove both the server-certificate NodeId and signed-response issuer must equal the verified expected peer. +- **Post-mortem**: Future transport reviews must enumerate missing metadata, ambiguous credential containers, and multi-valued identity fields as explicit negative cases. Compatibility exceptions require a connection-derived proof rather than absence of security metadata. +- **Verification**: Real dynamic-port TLS tests cover extra IP/DNS SANs, mixed/duplicate key blocks, listener survival after rejected handshakes, and signed response/TLS identity mismatch. Exact commands and binary output are retained under the ignored Task 8 evidence directory. + +## 2026-08-14 22:17 CST + +- **Change**: Added the provisional Task 8 peer mutual-TLS transport with exact private-overlay bind validation, explicit Authority roots, required client certificates, exact IP SANs, canonical NodeId certificate extensions, and bidirectional NodeId binding. +- **Files**: `src/transport/tls.rs`, `src/runtime/node.rs`, peer client/router boundaries, peer certificate issuance, Directory endpoint validation, `tests/http_mtls.rs`, `README.md`, dependency review, this roadmap, and ignored Task 8 report/evidence. +- **Trust binding**: Inbound identity comes only from the Rustls peer certificate and is compared with the signed `WireEnvelope.issuer_id`; the handler then performs the existing credential-chain and envelope signature verification. Outbound construction requires an expected peer NodeId obtained from a verified seed or signed Capability Manifest, and verifies that value against the server leaf before accepting the existing signed response envelope from the same issuer. +- **Transport policy**: Peer clients use one explicit CA, PKCS#8 identity, no system roots, no ambient proxy, no redirects, a two-second connect timeout, and a five-second request timeout. Non-loopback plaintext HTTP is rejected before connection. The server validates the declared boundary and exact listener IP before serving and requires the server leaf SAN to contain exactly that one IP. +- **Revocation policy**: An explicitly revoked client leaf is rejected during TLS. Stale state still permits TLS health/diagnostic access, while the revocation-aware effectful Directory registration returns typed `RevocationStateStale`; a rejected handshake does not terminate the listener. +- **Certificate profile**: Added a provisional dual-use private-peer leaf (`ClientAuth` + `ServerAuth`) issued to an existing node CSR with one exact IP SAN and one noncritical canonical DER UTF8String NodeId extension. Enrollment persistence/config wiring remains Task 9/10 work and certificate rotation remains deferred. +- **Memory boundary**: Project-owned PEM private-key strings remain `Zeroizing` and Debug-redacted. Rustls, Reqwest, Hyper, allocator internals, TLS record buffers, kernel socket buffers, and remote peer memory necessarily make or retain copies outside AgenNet's zeroization guarantee. +- **Validation boundary**: Real TLS behavior is exercised on dynamic loopback ports, including missing/wrong/expired/revoked identities, wrong SAN, NodeId mismatch, redirect and proxy isolation. This does not claim physical two-device reachability; that remains the later acceptance gate. +- **Post-mortem**: Security boundary omission — the first client builder verified its leaf NodeId but only applied WebPKI IP SAN verification to the remote server. Because TLS client authentication does not compare a client SAN with its source address, that allowed a locally loaded peer leaf issued for boundary IP A to authenticate while configured for IP B. Client construction now requires exactly one IP SAN equal to `NetworkBoundary.bind_ip` before Reqwest is created; a test records the pre-network rejection. Future dual-use certificate reviews must enumerate both directions independently rather than assuming server-name verification is symmetric. + +## 2026-08-15 04:20 CST + +- **Change**: Added provisional v0.2 signed revocation snapshots, monotonic owner-only Authority/cache persistence, stale-first policy guards, a signed snapshot endpoint, and a resilient no-proxy refresh loop. +- **Files**: `src/protocol/revocation.rs`, `src/runtime/revocation.rs`, `src/transport/revocation.rs`, adjacent Authority/error/module/Directory boundaries, `tests/revocation.rs`, `tests/http_revocation.rs`, `Cargo.toml`, this roadmap, and ignored Task 7 brief/report/evidence. +- **Decision**: The online Authority uses a dedicated `PublishRevocationSnapshot` scope and exact-byte Ed25519 proof. Epochs start at one; exact replay is idempotent; changed same-epoch data and rollback fail. Freshness expires at `now >= next_update_ms`, so stale data never authorizes effectful work. A snapshot may revoke its issuer once and then freezes that issuer against every higher epoch. +- **Persistence**: Authority epoch and node cache formats are bounded and versioned, use 0700 directories and 0600 no-follow regular files, publish atomically with file and directory sync, and re-verify persisted signatures plus stable publisher binding on restart. Same-key Root credential renewal is accepted; key/issuer rollover requires a future explicit migration. +- **HTTP behavior**: Directory registration is guarded only after signed-envelope verification; route query, health and snapshot refresh stay available. The refresh client disables proxies and redirects, bounds responses, retains the last verified cache through transient failures, reports a typed last error, and retries until watch-based shutdown. +- **Error record**: Technical blind spot — the first paused-time refresh test mixed real sockets with Tokio's auto-advanced timeout clock, then an intermediate test server let accepted sockets inherit nonblocking mode and used arbitrary wall-clock polling for assertions. Under full-suite load this produced a false `RequestFailed`. The scheduler is now tested with a pure async closure under paused time, real 503-to-200 recovery uses normal time and `watch` diagnostic events, accepted streams are explicitly blocking, and the only remaining one-millisecond poll is a bounded server `accept` loop rather than state synchronization. +- **Prevention**: Never combine paused Tokio time with external socket progress. Use event channels for async state assertions, separate pure scheduling from transport, and verify concurrency fixes with repeated focused runs plus the complete parallel suite. +- **Boundary**: The existing Directory still carries a fixed validation timestamp, and Task 7 only integrates the concrete registration endpoint plus a central guard. Task 12 must compose a live clock and apply the guard to all effectful Runtime routes. Task 8 must authenticate TLS identity before calling the verified-credential guard. Cross-process writer exclusion remains Task 9 work. +- **Deferred must-fix**: Task 5 `encode_handoff` still uses growable `serde_json::to_vec` before the result is placed in `Zeroizing`. This TTY handoff path must be corrected before Task 10 enables the CLI; Task 7 deliberately does not change it. + +## 2026-08-14 21:52 CST + +- **Change**: Hardened Task 7 after review by pinning every revocation cache to one Root-authorized stable publisher binding, poisoning mutation after uncertain persistence, filtering Directory candidates against current revocation state, and removing the raw credential-chain enforcement API. +- **Files**: `src/protocol/envelope.rs`, `src/runtime/directory.rs`, `src/runtime/error.rs`, `src/runtime/revocation.rs`, `src/transport/directory.rs`, `tests/revocation.rs`, `tests/http_revocation.rs`, `ROADMAP.md`, and ignored Task 7 review report/evidence. +- **Root cause**: Security boundary omission — the first cache accepted any Root-authorized publish credential in the domain, a post-rename directory-sync error left disk and memory potentially divergent without freezing later writes, and Directory query returned a previously registered provider without reevaluating revocation. The enforcement helper also accepted a raw `CredentialChain`, making its verified-chain precondition caller-enforced rather than type-enforced. +- **Solution**: Persist and validate a v0.3 stable `(domain, authority_id, signing key)` publisher binding with an explicit publish scope; accept same-key Root credential renewal but reject issuer/key rollover. Treat every persistence error as mutation poison until restart. Carry verified claims out of the single envelope-open operation into a private revocation subject, store it with registration, and return only `CurrentAndAllowed` candidates; stale policy returns HTTP 200 with an empty set. Reject stale incoming snapshots and higher-epoch timestamp regression before persistence. +- **Post-mortem**: The initial tests emphasized signature/epoch validity but did not model publisher substitution, uncertain rename durability, or the time gap between registration and routing. Future security reviews must enumerate stable trust pins, explicitly model every atomic-write error point, and retest authorization at each use boundary rather than only at admission. +- **Compatibility (superseded by Task 10 review round 2)**: The raw `DirectoryRegistry::register` mutation had no consumer and was removed because it bypassed the signed capability ceiling. Query compatibility remains; cache persistence is deliberately versioned to v0.3 and fails closed on v0.2, while publisher rollover requires a future explicit migration. + +## 2026-08-15 01:18 CST + +- **Change**: Replaced enrollment request `serde_json::to_vec` serialization with one fixed-capacity zeroizing writer shared by HTTP transmission and exact-request digesting. +- **Files**: `src/bootstrap/enrollment.rs`, `src/bootstrap/mod.rs`, `src/transport/enrollment.rs`, `ROADMAP.md`, and ignored Task 6 report/evidence. +- **Root cause**: Security implementation gap — wrapping the final `Vec` in `Zeroizing` did not protect earlier allocations released by `Vec` growth. A real request characterization showed capacity growing from 0 to 2048 while serializing the bearer. +- **Solution**: Preallocate the complete 256 KiB enrollment request limit inside `Zeroizing>`; route Serde through a custom `Write` implementation that checks `current_len + incoming_len` before every append and fails before mutation when the bound would be exceeded. Move that same zeroizing allocation directly into `Bytes::from_owner`, and compute the durable exact-request digest from the same helper. +- **Prevention**: For secret serialization, audit allocation history rather than only the final owner. A security limit must be enforced before growth, not checked after growth, and all consumers of the same secret wire representation must share one serialization primitive. +- **Resource boundary**: Each enrollment request temporarily reserves 256 KiB of userspace capacity so successful and rejected serialization never reallocates. The allocation is bounded and released/zeroized at request completion; third-party and kernel buffers remain outside the project guarantee. + +## 2026-08-15 00:48 CST + +- **Change**: Corrected Task 6 enrollment bearer ownership, request-body zeroization, pre-reservation Authority binding, CA policy validation, Node ID DER canonicality, and CSR negative coverage after security review. +- **Files**: `src/bootstrap/enrollment.rs`, `src/bootstrap/invitation.rs`, `src/transport/enrollment.rs`, `tests/http_enrollment.rs`, `Cargo.toml`, `Cargo.lock`, and ignored Task 6 report/evidence. +- **Root cause**: Security boundary omission — the first implementation borrowed `InvitationHandoff`, made a normal request-body `Vec` copy, validated the pinned presented CA without requiring its exact Authority KeyUsage policy, and deferred configured Authority identity checks until after the invitation authorization boundary. It also accepted non-minimal DER length encodings and used a malformed rather than parseable-signature-tampered CSR fixture. +- **Solution**: Consume the handoff by value; transfer a zeroizing owner directly into `Bytes::from_owner`; require exact CA pathLen/KeyUsage and exact leaf usages; bind invitation domain, Authority endpoint, Root fingerprint, Authority CA fingerprint and live PKI fingerprint before reservation; require canonical DER and a non-critical unique Node ID extension; and test a structurally valid CSR whose signature bit is changed. +- **Prevention**: Treat bearer ownership and every serialization allocation as part of the secret lifecycle; enumerate configured trust values at the authorization boundary; test certificate policy with real positive and negative DER/TLS handshakes; and distinguish parser rejection from cryptographic verification rejection. +- **Retry boundary**: Because enrollment consumes the in-memory handoff, a caller that must retry after uncertain delivery has to reacquire the same invitation from its secure external handoff source while reusing the already persisted operation identity and local keys. No clone or raw-secret recovery API was added. +- **Memory boundary**: AgenNet zeroizes its `SecretString`, exact request buffers, and project-owned Reqwest body owner. Hyper, Rustls, kernel socket buffers, TLS record buffers, and remote peer memory remain outside this guarantee. + +## 2026-08-14 23:55 CST + +- **Change**: Implemented provisional v0.2 enrollment with exact-byte Ed25519 request proof, locally verified handoff claims, fingerprint-pinned WebPKI TLS, Authority-issued credentials and CSR certificates, and exact durable lost-response recovery. +- **Files**: `src/protocol/enrollment.rs`, `src/bootstrap/enrollment.rs`, `src/transport/enrollment.rs`, adjacent module/error/policy boundaries, `tests/enrollment_protocol.rs`, `tests/http_enrollment.rs`, `tests/invitation_store.rs`, `Cargo.toml`, and `Cargo.lock`. +- **Decision**: The joining node receives only the Authority HTTPS endpoint and CA DER fingerprint. The server presents leaf plus CA; the client selects the exact fingerprinted CA, validates its self-signature/CA constraints/validity, constructs an only-that-root WebPKI verifier, and then validates the leaf chain, signature, validity, and exact IP SAN. No system roots, proxy, redirect, DNS endpoint, or permissive TLS switch participate. +- **Recovery ordering**: Reserve invitation, validate and issue a candidate, atomically publish and sync an owner-only exact-result record, durably consume the invitation, then return. Recovery requires the exact invitation, operation ID, and signed-request digest and returns the byte-equivalent stored bundle. The result file and invitation journal are deliberately not described as one atomic transaction; post-publish retries reconcile consumption before returning and fail closed if reconciliation is not durable. +- **Version boundary**: Public enrollment claims and request-signature domain separation consistently use `agenet.enrollment.v0.2`; v0.1, v1, and unknown future versions fail before transport or reservation. +- **Error record**: Misunderstood requirement — an intermediate implementation treated invitation expiry as a credential/certificate lifetime ceiling. Invitation expiry is only the redemption deadline; issued identity expiry is bounded by Authority credential expiry and `maximum_node_lifetime_ms` (and the issuing CA validity), so a short-lived invitation does not create an immediately expiring node. +- **Prevention**: Name authorization deadlines separately from issued-resource validity, encode each bound in one policy helper, and retain a regression assertion that a valid node credential can expire after its invitation redemption deadline. Security-sensitive HTTP tests are split by scenario; proxy environment mutation runs only in an isolated one-test child process. +- **Boundary**: This verifies enrollment over real loopback TLS and crash-boundary recovery semantics. It does not claim multi-host reachability, peer mTLS lifecycle, revocation, service installation, or atomicity across independent persistence logs. + +## 2026-08-14 19:12 CST + +- **Change**: Bound every one-time invitation public claim to both the Authority's persisted HMAC and a locally verifiable bearer-secret HMAC; replaced the public raw-secret accessor with opaque `InvitationAuthentication` and required reservation to present the complete claims boundary. +- **Files**: `src/bootstrap/invitation.rs`, `src/bootstrap/mod.rs`, `tests/invitation_store.rs`, `ROADMAP.md`; ignored review evidence and the amended Task 5 report under `.superpowers/sdd/01-v1-multi-host-node-bootstrap/`. +- **Decision**: Encode all claims with a deterministic domain-separated, u32-length-prefixed binary format, persist its SHA-256 digest, bind that digest into the pepper-keyed server HMAC, and carry a separate secret-keyed claims-integrity HMAC in the complete handoff. Verify the local tag before network use and verify the same incoming digest during Authority reservation. +- **Reason**: A bearer secret authorizes enrollment only for the exact Authority endpoint, trust fingerprints, domain, directory seeds, profile, capability ceiling, invitation ID, expiry, and protocol policy that the Authority issued. +- **Error record**: Technical blind spot — Task 5 initially authenticated only `invitation_id + secret`, so a party able to rewrite public handoff claims without reading the hidden secret could substitute an attacker endpoint and trust anchor. The same implementation also exposed a public `SecretString` accessor despite the intended TTY/enrollment-only boundary. +- **Prevention**: For every split public-claims/bearer protocol, enumerate and deterministically encode every claim, bind the digest at both local handoff and server authorization boundaries, and test independent mutation of every field plus tag and secret. Secret-bearing types must expose only opaque, redacted capabilities; raw materialization requires a crate-private serialization boundary. +- **Compatibility**: The insecure Task 5 v1 format existed only in the unmerged review commit and has no supported consumer. Corrected writes explicitly emit provisional v2 for both handoff and journal; v1 and unknown future versions return `UnsupportedInvitationFormat`. There is no automatic v1 journal migration because the missing claims digest cannot be reconstructed from its persisted record, and no fallback accepts the insecure handoff shape. + +## 2026-08-14 18:34 CST + +- **Change**: Implemented the Task 5 durable, one-time invitation store with 256-bit secret generation, HMAC-only persistence, an independently stored owner-only pepper, bounded expiry and lockout, two-phase reserve/consume/release, idempotent winning operations, and crash-safe replay. +- **Files**: `src/bootstrap/invitation.rs`, `src/bootstrap/journal.rs`, `src/bootstrap/mod.rs`, `src/protocol/types.rs`, `src/protocol/mod.rs`, `tests/invitation_store.rs`, `ROADMAP.md`; ignored Task 5 reports and evidence under `.superpowers/sdd/01-v1-multi-host-node-bootstrap/`. +- **Decision**: Keep the invitation handoff and journal as explicit provisional versioned formats; authenticate known records with constant-time HMAC verification and use the same verification path with a dummy digest for unknown IDs; append a length-delimited checksummed event and `sync_data` before applying its validated projection delta; fail closed after uncertain persistence. +- **Reason**: Enrollment needs a bounded, auditable one-time authorization primitive whose secret is never persisted in recoverable form and whose winner survives process restart without duplicating consumption. +- **Security boundary**: This store assumes one Authority process owns a state directory. Cross-process exclusive journal locking remains a Task 9 service-lifecycle requirement; Task 5 does not claim multi-process writers or implement Task 6 enrollment/CLI behavior. +- **Error record**: Technical blind spot — an intermediate design deduplicated failed authentication by untrusted `operation_id`, allowing an attacker to reuse one operation ID with changing wrong secrets and avoid the fifth-failure lockout. +- **Prevention**: Never deduplicate failed authentication solely by a caller-controlled operation ID. Every failed candidate now increments the durable counter, and a regression test verifies that five distinct wrong secrets sharing one operation ID reach `Locked`. +- **Verification**: Focused invitation and handoff tests, full all-target tests, formatting, and all-target/all-feature Clippy with warnings denied passed; exact commands and logs are retained in the ignored Task 5 evidence directory. + +## 2026-08-14 13:16 CST + +- **Change**: Converted the approved v0.2 design into three dependency-ordered, test-first implementation plans for multi-host bootstrap, installation surfaces, and the bilingual public site. +- **Files**: `plan/01-v1-multi-host-node-bootstrap.md`, `plan/02-v1-installation-surfaces.md`, `plan/03-v1-public-site.md`, `docs/superpowers/specs/2026-08-14-node-bootstrap-and-pages-design.md`, `ROADMAP.md`. +- **Decision**: Implement secure node semantics before distribution, then publish one canonical install/Agent workflow, and only then deploy the A3 Field Study site. Every task has a failing-test boundary, stable interfaces, verification commands, documentation updates, and an independent five-section commit. +- **Reason**: This order prevents a polished installer or website from getting ahead of a real credential, TLS, revocation, recovery, and two-device acceptance path. +- **Security clarification**: Tailscale listeners must be assigned to the local interface, not merely fall inside CGNAT space; enrollment and peer clients bypass ambient system proxies, while the external LLM client retains proxy support. +- **Boundary**: These files are executable plans, not evidence that v0.2 is implemented. Public multi-host claims remain blocked on the physical two-device gate. + +## 2026-08-14 12:54 CST + +- **Change**: Approved the v0.2 design for secure private-overlay Node Bootstrap, layered human/Agent installation surfaces, and a bilingual AgenNet GitHub Pages site. +- **Files**: `docs/superpowers/specs/2026-08-14-node-bootstrap-and-pages-design.md`, `ROADMAP.md`; ignored visual studies under `.superpowers/brainstorm/`. +- **Decision**: Use a protocol-aware `agenet` CLI as the single state-changing path; add an Authority credential chain, TLS-pinned one-time enrollment, user-level macOS/Linux services, a versioned generic Agent guide and Codex Skill, Astro/Starlight documentation, and the approved A3 Field Study visual direction. +- **Reason**: A shortcut or polished site is useful only if it represents a real, recoverable, revocable multi-machine onboarding path rather than wrapping the loopback demo or duplicating security logic across scripts and Skills. +- **Boundary**: The design remains a Developer Preview and defers public-Internet transport, Windows, root daemons, automatic updates, hosted control plane, automatic Agent exposure, arbitrary remote shell, and live node state on GitHub Pages. + +## 2026-08-14 00:35 CST + +- **Change**: Passed the real Walkman-backed four-process demo after the v2 provider correction. +- **Files**: Runtime evidence retained under ignored `.local/demo/3cf7bccc-932b-4b77-9ae5-514f8a52f961/`; validation status updated in `ROADMAP.md` and `docs/design/agenet-v0.1.md`. +- **Decision**: Mark the loopback MVP's real-model gate verified while keeping all multi-machine, TLS, sandbox, quota, and failover claims deferred. +- **Reason**: The real model produced a valid Intent in one call; Executor and Verifier independently matched Artifact `sha256:ab50610cf384a1553be8b36341366601efbf03c02b2568719ca150f07b64cd50` at 151 bytes, 4 lines, and 4 non-empty lines; the source Contract reached `Accepted` in 2047 ms total. +- **Evidence**: Four distinct PIDs and loopback ports were observed and reaped; peer HTTP counters were 13 requests, 17,699 bytes sent, and 36,701 bytes received; log scanning found no Authorization header, bearer value, API-key variable, or `ak` query string. + +## 2026-08-14 00:20 CST + +- **Change**: Corrected the Walkman Decision Adapter from an assumed OpenAI route to the verified `gemini_multimodal_inline_v1` ModelHub contract; added safe upstream status classification and provider-style contract tests. +- **Files**: `src/adapters/decision.rs`, `src/runtime/requester.rs`, `src/node.rs`, `tests/llm_adapter.rs`, `tests/multiprocess_demo.rs`, `plan/00-v2-modelhub-adapter-correction.md`, `README.md`, `docs/design/agenet-v0.1.md`. +- **Decision**: Keep OpenAI and ModelHub styles explicit; the Walkman demo posts to the complete endpoint with `ak` query authentication and inline text. +- **Reason**: The real demo consistently returned HTTP 404 before any protocol work. Inspection of Walkman and QueryAgent showed that `OPENAI_BASE_URL` is only a compatibility alias for a complete ModelHub endpoint. +- **Error record**: Technical blind spot — the initial implementation inferred provider semantics from an environment-variable name and collapsed all adapter failures to `DecisionFailed`. +- **Prevention**: Before integrating an inherited endpoint, inspect the owning provider's API-style setting and reference implementation; add a contract test for exact path, authentication placement, body schema, response schema, and sanitized error category. + +## 2026-08-13 23:50 CST + +- **Change**: Added the real OpenAI-compatible Decision Adapter, node CLI, four-process demo harness, retained audit state, graceful SIGTERM shutdown, and end-to-end process test. +- **Files**: `src/node.rs`, `src/demo.rs`, `src/main.rs`, `fixtures/sample.rs`, `tests/multiprocess_demo.rs`, `README.md`, `docs/design/agenet-v0.1.md`. +- **Decision**: Keep the demo outside the protocol control plane; pass only the Directory seed to Requester bootstrap and only the three allowlisted model variables to that child. +- **Reason**: Demonstrate actual process/network/identity boundaries without confusing harness convenience with an AgenNet architectural primitive. + +## 2026-08-13 23:30 CST + +- **Change**: Implemented the real source-metrics workload, Contract-authorized Artifact reads, provider execution, independent verification, and evidence-gated acceptance. +- **Files**: `src/adapters/`, `src/runtime/provider.rs`, `src/runtime/requester.rs`, `src/transport/node.rs`, `tests/source_metrics.rs`, `tests/http_artifact.rs`, `tests/llm_adapter.rs`. +- **Decision**: Keep Executor and Verifier metric implementations separate and compare both Evidence claims again at the Requester before emitting `Accepted`. +- **Reason**: Prevent delivery, a single implementation, or a provider-controlled verifier from being sufficient for acceptance. + +## 2026-08-13 23:10 CST + +- **Change**: Implemented durable per-node storage and the loopback Directory/HTTP transport baseline. +- **Files**: `src/runtime/`, `src/transport/`, `tests/runtime_storage.rs`, `tests/http_directory.rs`, `tests/http_client.rs`. +- **Decision**: Use content-addressed Artifact files, owner-only private-key files, serialized append-and-sync journal writes, and explicit loopback URL validation. +- **Reason**: Make recovery, tamper detection, request limits, timeout behavior, and signed discovery independently testable before orchestration. + +## 2026-08-13 22:30 CST + +- **Change**: Implemented the pure signed protocol kernel and its unit/property tests. +- **Files**: `Cargo.toml`, `Cargo.lock`, `src/protocol/`, `tests/protocol_kernel.rs`, `README.md`, `docs/design/agenet-v0.1.md`. +- **Decision**: Sign preserved payload bytes instead of depending on JSON canonicalization; isolate network and disk behavior from the protocol module. +- **Reason**: Make identity, authorization, Contract transition, idempotency, and tamper-rejection rules testable without runtime side effects. + ## 2026-08-13 22:00 CST - **Change**: Initialized the AgenNet project with a provisional v0.1 design and a single-machine, multi-process MVP plan. - **Files**: `README.md`, `CONTEXT.md`, `docs/design/agenet-v0.1.md`, `plan/00-v1-local-loopback-mvp.md`, `.gitignore`, `.env.example`. - **Decision**: Start with a real read-only source-metrics workflow on isolated loopback processes. Preserve future multi-machine protocol semantics while deferring TLS, arbitrary code execution, replication, quota, and federation. - **Reason**: Validate the smallest honest coordination loop before expanding deployment and workload complexity. - diff --git a/docs/bootstrap/agent-node-setup.en.md b/docs/bootstrap/agent-node-setup.en.md new file mode 100644 index 0000000..fa4c7dd --- /dev/null +++ b/docs/bootstrap/agent-node-setup.en.md @@ -0,0 +1,72 @@ +# AgenNet Agent Node Setup Guide + +Status: **v0.2.0-preview.4 — Developer Preview — physical acceptance pending**. + +This guide lets an Agent prepare a macOS, Linux, or WSL2 device as an AgenNet +node. native Windows is unsupported. You may install the public binary and +inspect public systemd and Tailscale/WireGuard readiness. You must not receive, +read, repeat, or fill an Invitation, passphrase, private key, token, private +address, CIDR, or raw terminal output. + +## Fixed installation procedure + +Use only the fixed release; never replace it with a moving tag or branch. Run +these commands locally: + +```sh +set -eu +release='https://github.com/Nexa-Language/AgenNet/releases/download/v0.2.0-preview.4' +work=$(mktemp -d "${TMPDIR:-/tmp}/agenet-bootstrap.XXXXXX") +trap 'rm -rf -- "$work"' EXIT HUP INT TERM +curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --max-redirs 5 --connect-timeout 10 --max-time 120 --output "$work/install.sh" "$release/install.sh" +curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --max-redirs 5 --connect-timeout 10 --max-time 120 --output "$work/SHA256SUMS" "$release/SHA256SUMS" +expected=$(awk '$2 == "install.sh" { print $1 }' "$work/SHA256SUMS") +if command -v sha256sum >/dev/null 2>&1; then actual=$(sha256sum "$work/install.sh" | awk '{print $1}'); else actual=$(shasum -a 256 "$work/install.sh" | awk '{print $1}'); fi +test -n "$expected" && test "$actual" = "$expected" +sh "$work/install.sh" +``` + +Run the following, then summarize only public conclusions rather than raw +output: + +```sh +agenet --version +agenet node doctor --output json +``` + +Confirm that the environment is not native Windows; WSL2 has an available +systemd user session; exactly one Tailscale or WireGuard address matches the +expected policy; and no different AgenNet binary or managed node state already +exists. On ambiguity, stop with a stable public error code. Never guess an +address or weaken a security control. + +## TTY boundary + +Do not perform enrollment. Do not ask the user to paste an Invitation into +chat, and do not place it in arguments, environment variables, a transfer +file, or stdin. When preparation is complete, reply verbatim: + +```text +Action required in your local terminal: + agenet node join +Do not paste the invitation or terminal output into this chat. +Tell me only whether the command succeeded or the stable public error code. +``` + +After the user reports only success or a stable public error code, you may run +the public checks: + +```sh +agenet node start --output json +agenet node status --output json +agenet node doctor --output json +``` + +Report only the version, whether the service is running, and the stable public +doctor status. Never display or inspect AgenNet private state files. + +## One-sentence Agent trigger + +```text +Read the official AgenNet node setup guide and use fixed v0.2.0-preview.4 to prepare this WSL2 machine as a Provider node; let me enter every Invitation and password only in my local TTY. +``` diff --git a/docs/bootstrap/agent-node-setup.md b/docs/bootstrap/agent-node-setup.md new file mode 100644 index 0000000..008769b --- /dev/null +++ b/docs/bootstrap/agent-node-setup.md @@ -0,0 +1,66 @@ +# AgenNet Agent 节点安装指南 + +状态:**v0.2.0-preview.4 — Developer Preview — physical acceptance pending**。 + +本指南供 Agent 把一台 macOS、Linux 或 WSL2 设备准备成 AgenNet 节点。 +native Windows 不受支持。你可以安装公开二进制、检查 systemd 与 +Tailscale/WireGuard 的公开就绪状态,但不能接收、读取、转述或代填 +Invitation、passphrase、私钥、token、私有地址、CIDR 或原始终端输出。 + +## 固定安装步骤 + +只使用固定 release,不得改成 moving tag 或分支。逐条在本机执行: + +```sh +set -eu +release='https://github.com/Nexa-Language/AgenNet/releases/download/v0.2.0-preview.4' +work=$(mktemp -d "${TMPDIR:-/tmp}/agenet-bootstrap.XXXXXX") +trap 'rm -rf -- "$work"' EXIT HUP INT TERM +curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --max-redirs 5 --connect-timeout 10 --max-time 120 --output "$work/install.sh" "$release/install.sh" +curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --max-redirs 5 --connect-timeout 10 --max-time 120 --output "$work/SHA256SUMS" "$release/SHA256SUMS" +expected=$(awk '$2 == "install.sh" { print $1 }' "$work/SHA256SUMS") +if command -v sha256sum >/dev/null 2>&1; then actual=$(sha256sum "$work/install.sh" | awk '{print $1}'); else actual=$(shasum -a 256 "$work/install.sh" | awk '{print $1}'); fi +test -n "$expected" && test "$actual" = "$expected" +sh "$work/install.sh" +``` + +执行并只总结公开结论,不回传原始输出: + +```sh +agenet --version +agenet node doctor --output json +``` + +确认以下事实:运行环境不是 native Windows;若在 WSL2,systemd user +session 可用;设备只有一个符合预期策略的 Tailscale 或 WireGuard 地址; +没有现成的不同 AgenNet 二进制或已管理节点状态。遇到不确定性时停止并给出 +稳定公开错误码,不要猜地址或改安全配置。 + +## TTY 边界 + +不要运行 enrollment,不要让用户把 Invitation 粘贴到聊天,也不要把它放进 +参数、环境变量、文件代传或 stdin。准备工作完成后,原样回复: + +```text +Action required in your local terminal: + agenet node join +Do not paste the invitation or terminal output into this chat. +Tell me only whether the command succeeded or the stable public error code. +``` + +用户只报告成功或 stable public error code 后,你可以运行公开检查: + +```sh +agenet node start --output json +agenet node status --output json +agenet node doctor --output json +``` + +只报告版本、服务是否运行和 doctor 的稳定公开状态。不得展示或读取 AgenNet +私有状态文件。 + +## 给 Agent 的一句话 + +```text +阅读 AgenNet 官方节点安装指南,使用固定的 v0.2.0-preview.4 把这台 WSL2 机器准备成 Provider 节点;所有 Invitation 和密码只让我在本机 TTY 输入。 +``` diff --git a/docs/design/agenet-v0.1.md b/docs/design/agenet-v0.1.md index 6e860e0..f5ba8a8 100644 --- a/docs/design/agenet-v0.1.md +++ b/docs/design/agenet-v0.1.md @@ -9,20 +9,88 @@ AgenNet is a coordination substrate in which capabilities, intents, grants, cont - Heterogeneous Agents and deterministic resources are first-class participants. - Directory and Router return candidates; the Requester chooses and signs. - Every effect is authorized by deterministic Grant checks. +- Every delayed effect revalidates local identity, signed Contract parties, + Grant/Contract expiry, and the latest local revocation cache at its final + journal, network-send, or Artifact-read boundary. - Contracts are persistent and event-sourced; connections are transient. - `Delivered` and `Accepted` are different states. - Results carry evidence bound to immutable Artifact references. -- The protocol does not promise general exactly-once side effects. +- The protocol does not promise general exactly-once side effects. Revocation + propagation is not globally atomic: Recorder serializes a final check with + local journal mutation, Artifact access checks immediately before bytes are + read, and stale local policy fails closed. ## Loopback MVP The reference runtime is deployed as four independent processes with unique identities, ports, and state directories. The Requester knows only a Directory seed. It uses a real LLM adapter to translate a natural-language goal into a typed Intent, dynamically discovers an Executor and Verifier, signs two Contracts, and accepts only independently reproduced source metrics. -Peer traffic is signed with Ed25519 but uses plaintext HTTP restricted to loopback. Filesystem paths never appear in peer protocol objects; content is imported into a requester-owned content-addressed Artifact store and read only through a matching Contract and Grant. +Peer traffic is signed with Ed25519 and the current v0.2 demo also runs every peer hop through Authority-CA mTLS. Loopback aliases are probed and used when assigned; otherwise distinct dynamic ports share `127.0.0.1`. This is simulated transport evidence, not a physical multi-host claim. Filesystem paths never appear in peer protocol objects; content is imported into a requester-owned content-addressed Artifact store and read only through a matching Contract and Grant. + +## Signed protocol kernel + +The signed envelope preserves the originally serialized payload bytes and signs a domain-separated message containing `kernel_version`, `object_type`, `issuer_id`, payload length, and those exact bytes. String fields use a big-endian `u32` byte-length prefix and payloads use a big-endian `u64` byte-length prefix. Credentials and bilateral Contracts use separate `AGENET\0credential\0` and `AGENET\0contract\0` domains. A framing change therefore requires a kernel-version change and compatibility tests. + +Implemented and tested at the pure library layer: + +- Domain-issued, expiring Ed25519 Node Credentials; +- strict verification of exact-byte signed envelopes; +- scoped, expiring Artifact Grants; +- bilateral signatures over one Contract payload; +- deterministic Contract transitions and operation idempotency; +- hash-linked Event sequences; +- typed manifests, routes, intents, artifacts, metrics, evidence, and errors. + +These statements cover protocol-library invariants only. Durable storage, HTTP authorization, and the multi-process flow remain separate gates. + +## Runtime and transport baseline + +Each node state directory owns an append-only `journal.jsonl`, an Artifact directory where applicable, and a `0600` Ed25519 signing-key file. Journal entries are serialized under a mutex, flushed, and synchronized before the in-memory projection is advanced. Replay verifies both Contract and Event signatures again. + +The HTTP adapter rejects non-loopback Capability endpoints, caps JSON bodies at 256 KiB, maps failures to sanitized typed errors, and gives read-only requests a bounded retry path. Directory matching is deterministic equality on the public `kind.version`; it neither invokes a model nor selects a final provider. + +The protocol owns the only mapping from an exact Capability ID to its +versioned kind and required Provider role. Contract verification, Directory +registration, Provider dispatch, Event replay, and Artifact reads consume that +mapping; unknown IDs and ID/kind cross-pairs fail closed. Role mapping does not +replace Grant or capability-ceiling checks. The enrollment `Provider` profile +may explicitly authorize multiple permitted roles as a product choice, but an +operation still requires the exact signed role and invitation-derived ceiling. + +## Verified source-metrics flow + +The Requester imports UTF-8 source bytes, asks the Directory separately for `source.metrics.v1` and `source.metrics.verify.v1`, and signs a scoped bilateral Contract for each provider. The Executor and Verifier retrieve bytes through signed Artifact requests whose caller, Contract, Capability, Artifact hash, length, and expiry are checked. The two providers use separate metric implementations. The verification Contract links to the source Contract through `parent_contract_id`. + +Only the Requester can append `Accepted`, and it does so only after matching the Artifact reference and every SourceMetrics field. The Accepted Event carries the verification Contract ID and a hash of the verifying Evidence. A verifier failure leaves the source Contract at `Delivered`. + +## LLM and demo adapter + +The Decision layer receives only the natural-language goal, the strict Intent projection schema, and the public Capability kind. Two explicitly tested wire styles exist: `openai_chat_completions_v1` appends `/chat/completions` when required and uses a sensitive Authorization header; the Walkman-backed manual demo uses `gemini_multimodal_inline_v1`, treats the configured URL as a complete endpoint, places the credential in the `ak` query parameter, and sends inline text content. Neither style puts credentials in Debug output or logs. Both use `temperature: 0`, limit responses to 64 KiB, perform at most one real format-repair call, reject redirects, and have no manual-demo fallback. + +The demo provisions an ephemeral Domain Root, one Authority, four v0.3 Node Credentials, exact-IP peer certificates, and a current signed revocation snapshot. The credentials are least privilege: Directory-only, Requester-only, Executor-only with only `source.metrics.v1`, and Verifier-only with only `source.metrics.verify.v1`. It starts four copies of the `agenet node` binary on dynamic HTTPS loopback listeners, waits for mTLS health before signed Capability registration, submits one local pursuit with a bearer token read from a `0600` file, enforces a 90-second outer timeout, sends SIGTERM, and retains state for audit. + +## Validation matrix + +| Claim | Status | Evidence | +| --- | --- | --- | +| Exact-byte Credential, Envelope, Contract, and Event signatures | automated | unit/property tests | +| Grant and Artifact read scope | automated | protocol and Axum tests | +| Journal replay and operation idempotency | automated | restart test | +| Login-scoped service and host runtime | automated | validated bundle, live policy clock, exact bind, mTLS self-probe, readiness withdrawal | +| TLS credential renewal | automated/pending native | exact signed request, durable Authority idempotency, fresh CSR/key, generation pointer recovery; native service adoption gate required | +| Root-authorized node revocation | automated | exact Root signature, target/epoch/operation binding, replay and conflict tests | +| Leave and uninstall safety | automated | signed departure receipt/pending record, verified stop, exact managed-binary identity, TTY-only purge allowlist | +| Read-only diagnostics | automated | deterministic JSON, stable exit codes, no-proxy bounded checks, sentinel redaction | +| Independent source metric reproduction | automated | separate implementations plus third oracle | +| Four PIDs and four dynamic ports | automated | real child-process test | +| Real ModelHub Intent projection | verified locally | Walkman env run `3cf7bccc-932b-4b77-9ae5-514f8a52f961` | +| Four-process loopback mTLS session | automated | aliases when available, otherwise distinct loopback ports; not physical hosts | +| Physical secure multi-machine sessions | not verified | deferred to Task 14 | +| Sandbox for arbitrary code | not implemented | deferred | +| Quota, replication, failover, and federation | not implemented | deferred | ## Deliberately deferred -- TLS and cross-machine peer sessions +- physical cross-machine acceptance - replicated state and Recorder failover - quota reservation and revocation - leases, checkpoints, and relays @@ -32,3 +100,79 @@ Peer traffic is signed with Ed25519 but uses plaintext HTTP restricted to loopba Each deferred feature must preserve the MVP's protocol object and Capability-handler seams or document why evidence requires changing them. +## Node lifecycle and diagnostic boundary + +Credential renewal is a peer-mTLS effect, not enrollment bearer reuse. The +connection TLS NodeId must equal the strictly verified signed-envelope issuer, +and current credential/revocation policy is checked immediately before the +Authority mutation lock. The request proves possession of the unchanged Node +Ed25519 key and carries a fresh TLS CSR. The Authority returns the same NodeId, +Domain, roles, profile, and capability ceiling with a new credential/leaf; +exact operation replay returns the same durable result. + +Renewal persistence uses `identity-generations//` and one +`active-identity-v1.json`. Each generation is owner-only and contains the +credential, same Ed25519 private key, TLS certificate, new TLS private key, CA, +and a versioned manifest with exact hashes. Every material and directory is +synced before pointer publication. If a pointer exists, startup reads only its +generation and never falls back to legacy files. A pointer-before crash leaves +the old generation active; pointer-after recovery observes the actual pointer, +restarts the real user service, and requires runtime readiness plus mTLS health +with the new identity. Failed adoption rolls back only while the old credential +remains valid. Cleanup selects only a manifest-validated inactive UUID directory +with an exact allowlist; uncertainty retains it and emits a warning. + +Node revocation is a founding-host administrative flow. The operator must use a +controlling TTY, type the exact target NodeId, and unlock the Domain Root through +a hidden prompt. The short-lived Root authorization binds Domain, target, +expected current epoch, operation ID, and expiry. The online Authority preserves +both revoked sets and publishes epoch+1. Self/founding Authority revocation is +fail-closed and requires a future dedicated recovery design. + +Leave unregisters only manifests issued by the departing NodeId and records a +signed Directory receipt, or preserves a durable pending departure when the +Directory is unavailable. `Left` is written only after the service is verified +stopped; identity, config, credential generations, journal, and audit evidence +remain. Default uninstall deletes only the user-service artifact and a binary +whose trusted record still matches approved root, exact path/basename, owner, +mode, device, inode, size, hash, and version. Purge is controlling-TTY-only, +lists logical items first, requires exact NodeId plus `PURGE`, uses fixed +allowlists, and never deletes Root or founding Authority/admin material. + +Departure recovery reuses the exact persisted signed request and operation ID; +it does not mint a new request after local `Left`. The signed receipt binds the +Directory NodeId, request envelope, operation ID, and departing NodeId. Receipt +storage is synced before pending removal, so the pending-plus-receipt crash state +reconciles deterministically and tampered or cross-request receipts fail closed. + +Identity generation loading and cleanup use pinned directory descriptors. The +manifest key set and directory entry set must exactly equal the versioned +allowlist, and all five materials are bounded, hash checked, then decoded from +the same descriptor-relative bytes. Cleanup verifies the parent entry still +names the pinned device/inode before removing the directory; a rename or +attacker substitute is retained with a cleanup warning. + +Identity mutation has two lock scopes. The owner-only +`identity-operation-v1.lock` is exclusive and nonblocking across a full renew or +destructive purge workflow. The `0700` identity-generation directory descriptor +is the equivalent active-pointer lock: readers use shared `flock`, while +pointer publish/rollback, cleanup, and purge use exclusive `flock`. Cleanup +re-reads the pointer only after acquiring exclusive ownership and retains that +lock through child unlink, parent inode comparison, and sync. Pointer writers +release it before service restart, allowing HostRuntime to load under a shared +lock while the higher lifecycle operation remains serialized. + +These are advisory same-euid coordination locks, not a defense against a +malicious process already executing as the node account. The filesystem +boundary still requires `0700` generation directories and descriptor-relative +`O_NOFOLLOW` regular owner/mode-checked child access. Same-directory leaf +replacement by a same-euid attacker is documented outside the v0.2 threat +model; physical host compromise is not represented as prevented. + +Doctor never creates, refreshes, repairs, or rewrites state. It bounds reads and +network time, disables proxy and redirects, and checks owner/mode/type/symlink, +config schema, full credential/TLS/key binding and expiry, bind ownership, +signed revocation epoch/freshness/clock relation, Authority/Directory health, +service/process/readiness, and exact managed-binary metadata. Output omits full +paths, usernames, private IPs, raw credentials, prompts, invitations, and all +secret material. diff --git a/docs/install/index.en.md b/docs/install/index.en.md new file mode 100644 index 0000000..c33d092 --- /dev/null +++ b/docs/install/index.en.md @@ -0,0 +1,70 @@ +# Install AgenNet + +Status: **v0.2.0-preview.4 — Developer Preview — physical acceptance pending**. + +AgenNet currently supports macOS arm64/x86_64 and Linux arm64/x86_64. On +Windows, use the Linux build inside WSL2; native Windows is not supported. +Running a node also requires a private Tailscale or WireGuard overlay. The +persistent Linux/WSL2 service requires a working systemd user session. + +## Verify and install + +The command below installs only the public binary at `~/.local/bin/agenet`. It +downloads the installer and `SHA256SUMS` from the fixed release, verifies the +installer digest, and only then executes it. It does not read an Invitation, +password, node key, or model key. + +```sh +set -eu +release='https://github.com/Nexa-Language/AgenNet/releases/download/v0.2.0-preview.4' +work=$(mktemp -d "${TMPDIR:-/tmp}/agenet-bootstrap.XXXXXX") +trap 'rm -rf -- "$work"' EXIT HUP INT TERM +curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --max-redirs 5 --connect-timeout 10 --max-time 120 --output "$work/install.sh" "$release/install.sh" +curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --max-redirs 5 --connect-timeout 10 --max-time 120 --output "$work/SHA256SUMS" "$release/SHA256SUMS" +expected=$(awk '$2 == "install.sh" { print $1 }' "$work/SHA256SUMS") +if command -v sha256sum >/dev/null 2>&1; then actual=$(sha256sum "$work/install.sh" | awk '{print $1}'); else actual=$(shasum -a 256 "$work/install.sh" | awk '{print $1}'); fi +test -n "$expected" && test "$actual" = "$expected" +sh "$work/install.sh" +``` + +If the installer reports that the user binary directory is missing from PATH, +add `~/.local/bin` to the shell PATH before continuing. Check public state: + +```sh +agenet --version +agenet node doctor --output json +``` + +## Create the first Domain + +Domain creation, the Root passphrase, and Invitation display require a local +controlling TTY. Replace the placeholders with this device's private-overlay +address and a narrow authorized CIDR. Do not paste real addresses, terminal +output, or keys into chat. + +```sh +agenet domain init --network tailscale --bind-ip --allowed-cidr --output json +agenet invite create --profile provider --ttl 10m --output json +agenet node start --output json +agenet node status --output json +``` + +Follow the CLI's TTY instructions to transfer the one-time Invitation to the +human at the target device. That human runs only this command in that device's +local terminal: + +```text +agenet node join +``` + +Then run `agenet node start --output json` and +`agenet node doctor --output json`. Share only stable public status or an error +code. Never share an Invitation, passphrase, private key, token, private +address, CIDR, or raw diagnostic log. + +## Current boundary + +This preview verifies signed identities, mTLS, Capability routing, two +Contracts, an independent Verifier, and a read-only source-metrics loop. It has +not passed the two-physical-device acceptance yet, is not an arbitrary-code +sandbox, and makes no Internet-scale, failover, or stable-protocol claim. diff --git a/docs/install/index.md b/docs/install/index.md new file mode 100644 index 0000000..6cbea63 --- /dev/null +++ b/docs/install/index.md @@ -0,0 +1,65 @@ +# 安装 AgenNet + +状态:**v0.2.0-preview.4 — Developer Preview — physical acceptance pending**。 + +AgenNet 当前支持 macOS arm64/x86_64 与 Linux arm64/x86_64。Windows 请在 +WSL2 中使用 Linux 版本;native Windows 暂不支持。节点运行还需要 +Tailscale 或 WireGuard 私有覆盖网络。Linux/WSL2 的常驻服务依赖可用的 +systemd user session。 + +## 校验并安装 + +下面的命令只安装公开二进制到 `~/.local/bin/agenet`。它从固定版本下载 +安装器与 `SHA256SUMS`,先核对安装器摘要,再执行安装器。它不读取 +Invitation、密码、节点密钥或模型密钥。 + +```sh +set -eu +release='https://github.com/Nexa-Language/AgenNet/releases/download/v0.2.0-preview.4' +work=$(mktemp -d "${TMPDIR:-/tmp}/agenet-bootstrap.XXXXXX") +trap 'rm -rf -- "$work"' EXIT HUP INT TERM +curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --max-redirs 5 --connect-timeout 10 --max-time 120 --output "$work/install.sh" "$release/install.sh" +curl --fail --silent --show-error --location --proto '=https' --proto-redir '=https' --max-redirs 5 --connect-timeout 10 --max-time 120 --output "$work/SHA256SUMS" "$release/SHA256SUMS" +expected=$(awk '$2 == "install.sh" { print $1 }' "$work/SHA256SUMS") +if command -v sha256sum >/dev/null 2>&1; then actual=$(sha256sum "$work/install.sh" | awk '{print $1}'); else actual=$(shasum -a 256 "$work/install.sh" | awk '{print $1}'); fi +test -n "$expected" && test "$actual" = "$expected" +sh "$work/install.sh" +``` + +如果安装器提示 PATH 尚未包含用户目录,把 `~/.local/bin` 加入 shell 的 +PATH 后再继续。检查公开状态: + +```sh +agenet --version +agenet node doctor --output json +``` + +## 创建第一个 Domain + +Domain 创建、Root passphrase 与 Invitation 展示都要求本机 controlling +TTY。把占位符替换为这台设备在私有覆盖网络中的地址和被允许的窄 CIDR; +不要把真实地址、终端输出或密钥粘贴到聊天中。 + +```sh +agenet domain init --network tailscale --bind-ip --allowed-cidr --output json +agenet invite create --profile provider --ttl 10m --output json +agenet node start --output json +agenet node status --output json +``` + +按 CLI 的 TTY 提示把一次性 Invitation 交给目标设备上的人。目标设备上的 +人只在自己的本机终端执行: + +```text +agenet node join +``` + +随后可运行 `agenet node start --output json` 和 +`agenet node doctor --output json`。只分享稳定的公开状态或错误码;不要分享 +Invitation、passphrase、私钥、token、私有地址、CIDR 或原始诊断日志。 + +## 当前边界 + +这个预览版已验证签名身份、mTLS、Capability 路由、双 Contract、独立 +Verifier 与只读 source metrics 闭环。它尚未完成两台物理设备验收,不是 +任意代码 sandbox,也不承诺 Internet-scale、故障转移或稳定协议兼容性。 diff --git a/docs/releases/v0.2.0-preview.4.md b/docs/releases/v0.2.0-preview.4.md new file mode 100644 index 0000000..0c4a22e --- /dev/null +++ b/docs/releases/v0.2.0-preview.4.md @@ -0,0 +1,70 @@ +# AgenNet v0.2.0-preview.4 + +**Developer Preview — physical acceptance pending** + +This is the first installable AgenNet preview. It is intentionally published +as a prerelease rather than a stable release. + +## What works + +- native user-local binaries for macOS arm64/x86_64 and Linux arm64/x86_64; +- Linux installation inside WSL2; native Windows is not supported; +- a fixed-version checksum-verifying installer with no enrollment inputs; +- a distributable `agenet-node-bootstrap` Agent Skill; +- encrypted Domain Root material and Authority/Node credential chains; +- exact NodeId-bound mTLS peer transport over a private Tailscale or WireGuard + overlay; +- signed Capability discovery, bilateral Contracts, scoped Artifact access, + independent verification, and evidence-gated acceptance; +- a real read-only `source.metrics.v1` workload; +- login-scoped launchd and systemd user services; +- credential renewal, revocation, recoverable leave, conservative uninstall, + and public doctor/status commands. + +## Install and bootstrap + +Follow the [human installation guide](https://github.com/Nexa-Language/AgenNet/blob/v0.2.0-preview.4/docs/install/index.en.md) +or give an Agent the [Agent node setup guide](https://github.com/Nexa-Language/AgenNet/blob/v0.2.0-preview.4/docs/bootstrap/agent-node-setup.en.md). +Both pin this exact release and stop before enrollment so every Invitation and +passphrase is entered only by the human in the target device's local +controlling TTY. + +Release downloads include four native archives, `release-manifest-v1.json`, +`install.sh`, `SHA256SUMS`, both raw Agent guides, this release note, and the +versioned Skill archive. Every native archive is deterministic and has a +GitHub artifact attestation. The installer and complete asset set are verified +again before the prerelease can publish. + +## Compatibility and requirements + +- Rust-built native targets: Apple arm64, Apple x86_64, Linux arm64, and Linux + x86_64. +- Windows users need WSL2 with a working systemd user session. +- A node requires a private Tailscale or WireGuard overlay with one + unambiguous policy-approved address. +- Bootstrap and secret-bearing operations require a real controlling TTY. +- Persistent schemas and wire protocols are versioned and fail closed on old + or unknown formats. This preview does not promise stable compatibility. + +## Honest limits + +The preview has passed extensive protocol, filesystem, HTTP/TLS, process, +service-manager, and real-binary local mTLS tests. It has not yet passed the +planned two-physical-device private-overlay acceptance. The later fresh WSL2 +Agent run validates only the public installation/Skill surface and does not +substitute for that physical network gate. + +This release is not an arbitrary-code sandbox and does not expose a shell +execution Capability. It does not prove Internet-scale discovery, distributed +failover, replicated state, quota/payment markets, or general software +engineering ability. Its first workload is intentionally small and read-only. + +The Agent Society and collective-AGI direction is a long-term research vision, +not a capability claim for this release. + +## Safe error reporting + +Report only the stable public error code and whether the public doctor status +is healthy. Do not publish Invitations, passphrases, private keys, tokens, +private addresses, CIDRs, Node IDs, endpoints, local paths, raw JSON, terminal +transcripts, or diagnostic logs. diff --git a/docs/security/dependency-review-v0.2.md b/docs/security/dependency-review-v0.2.md new file mode 100644 index 0000000..0c8baa4 --- /dev/null +++ b/docs/security/dependency-review-v0.2.md @@ -0,0 +1,40 @@ +# v0.2 Dependency Review + +**Review scope:** the direct dependencies introduced for the v0.2 multi-host +bootstrap baseline, pinned in `Cargo.toml` and resolved in `Cargo.lock`. +**Review date:** 2026-08-14. + +The review uses each resolved crate's embedded Cargo metadata for license and +upstream repository data, and `cargo tree --offline` for the lockfile +footprint. “Maintained” means the selected release is supplied by its named +upstream repository and remains within a supported upstream release line; it +does not replace ongoing advisory monitoring. Every dependency is exact-pinned +so an update requires this review to be revisited. + +| Crate | License | Maintenance status | Purpose | Transitive footprint | Removal boundary | +| --- | --- | --- | --- | --- | --- | +| `axum-server` 0.8.0 | MIT | Maintained upstream (`programatik29/axum-server`) | HTTPS server integration for Authority and peer endpoints. | Shares the existing Axum/Hyper/Tokio stack; adds `tokio-rustls`, `rustls-pki-types`, and server support crates. | Remove if the v0.2 server transport is replaced with another reviewed server implementation. | +| `rustls` 0.23.43 | Apache-2.0 OR ISC OR MIT | Maintained upstream (`rustls/rustls`) | Rust TLS implementation and the selected AWS-LC cryptographic provider. | `aws-lc-rs`, `rustls-pki-types`, `rustls-webpki`, and `zeroize`; exactly one Rustls 0.23 line is resolved. | Remove only with the reviewed replacement of all peer and enrollment TLS. | +| `tokio-rustls` 0.26.4 | MIT OR Apache-2.0 OR ISC | Maintained upstream (`rustls/tokio-rustls`) | Expose the authenticated peer certificate from the Axum Server TLS stream so request extensions can carry a connection-derived NodeId. | Shares Tokio, Rustls 0.23, and `rustls-pki-types`; it was already resolved transitively by `axum-server` and is now a direct API dependency. | Remove if the server integration provides an equivalent authenticated peer-identity extension without direct stream access. | +| `tower` 0.5.3 | MIT | Maintained upstream (`tower-rs/tower`) | Apply the connection-derived TLS identity as an Axum request extension after the Rustls handshake. | Already shared by Axum; moving it from test-only to runtime adds no resolved package. | Return to test-only if peer identity propagation moves behind an Axum-owned API. | +| `rcgen` 0.14.9 | MIT OR Apache-2.0 | Maintained upstream (`rustls/rcgen`) | Create the Authority CA and leaf certificate material. | `aws-lc-rs`, `pem`, `rustls-pki-types`, `time`, and `yasna`; default `ring` support is disabled. | Remove if certificates are externally provisioned through a reviewed provider. | +| `rustls-pemfile` 2.2.0 | Apache-2.0 OR ISC OR MIT | Maintained upstream (`rustls/pemfile`) | Strict PEM decoding for local TLS key and certificate loading. | `rustls-pki-types` and `zeroize`. | Remove if no supported persistence format uses PEM. | +| `age` 0.12.1 | MIT OR Apache-2.0 | Maintained upstream (`str4d/rage`), explicitly beta/pre-1.0 | Encrypt the offline Domain Root material at rest. | Broad crypto and localization closure, including `age-core`, AEAD/HPKE primitives, `scrypt`, `secrecy`, and `zeroize`. | Remove only with a reviewed replacement for offline Root encryption and a versioned keystore migration. | +| `rpassword` 7.5.4 | Apache-2.0 | Maintained upstream (`conradkleinespel/rpassword`) | Read Root passphrases from a hidden terminal. | Small platform I/O closure: `libc` and `rtoolbox`. | Remove if a reviewed OS-native secure prompt replaces terminal passphrase entry. | +| `hmac` 0.13.0 | MIT OR Apache-2.0 | Maintained RustCrypto MACs project | HMAC-SHA-256 for invitation derivation and verification. | `digest` 0.11, `crypto-common`, `ctutils`, and `zeroize`. The separate `hmac` 0.12 is transitive to `age`, not used by AgenNet directly. | Remove when invitations use a different reviewed, versioned authenticator. | +| `ipnet` 2.12.1 | MIT OR Apache-2.0 | Maintained upstream (`krisprice/ipnet`) | Parse and validate explicit loopback/private-overlay network prefixes. | Optional `serde` support shares the existing Serde closure. | Remove if private-network policy no longer accepts CIDR configuration. | +| `directories` 6.0.0 | MIT OR Apache-2.0 | Maintained upstream (`soc/directories-rs`) | Locate per-user config and state directories across supported platforms. | `dirs-sys`, `option-ext`, and platform APIs. | Remove if all service storage is replaced by a reviewed platform abstraction. | +| `time` 0.3.55 | MIT OR Apache-2.0 | Maintained upstream (`time-rs/time`) | Handle certificate and protocol validity timestamps. | `deranged`, `num-conv`, `powerfmt`, and `time-core`; also used by `rcgen` and macOS `plist`. | Remove if a reviewed certificate/time abstraction makes this direct dependency unnecessary. | +| `plist` 1.10.0 | MIT | Maintained upstream (`ebarnard/rust-plist`) | Generate and validate macOS LaunchAgent property lists. macOS target only. | `base64`, `indexmap`, `quick-xml`, `serde`, and `time`; omitted from non-macOS targets. | Remove if LaunchAgent support is retired or moved behind another reviewed serializer. | + +## Duplicate-major investigation + +`cargo tree -d --offline` reports several duplicate-major packages, including +`hmac` 0.12/0.13 and `digest` 0.10/0.11. The older lines are confined to +`age`'s transitive cryptography closure; AgenNet's direct HMAC uses the pinned +0.13 line. No duplicate Rustls major version is present: TLS resolves only +`rustls` 0.23.43. `rcgen` has `default-features = false` and explicitly selects +`aws_lc_rs`, preventing its default `ring` provider from adding a second TLS +provider. These remaining dependency-family duplicates are expected by the +selected exact crate versions and require review again when `age` or the +crypto stack changes. diff --git a/docs/superpowers/specs/2026-08-14-node-bootstrap-and-pages-design.md b/docs/superpowers/specs/2026-08-14-node-bootstrap-and-pages-design.md new file mode 100644 index 0000000..2e0d647 --- /dev/null +++ b/docs/superpowers/specs/2026-08-14-node-bootstrap-and-pages-design.md @@ -0,0 +1,635 @@ +# AgenNet Node Bootstrap and Public Site Design + +**Status:** Approved for implementation planning +**Date:** 2026-08-14 +**Depends on:** AgenNet v0.1 loopback MVP at `fda7a6b` +**Target milestone:** AgenNet v0.2 Developer Preview + +## 1. Purpose + +This design extends the verified loopback MVP into the smallest honest +multi-machine AgenNet preview. A user must be able to create a private AgenNet +Domain on one machine, authorize a second macOS or Linux machine with a +short-lived invitation, keep that node running as a user service, discover it, +and revoke it. The same workflow must be accessible through a stable CLI, a +versioned guide that general coding Agents can follow, an optional Codex Skill, +and a bilingual GitHub Pages site. + +All defaults in this document remain revisable after real two-device testing. +Revisions must be versioned, justified in `ROADMAP.md`, and preserve or migrate +public protocol and persistent-state consumers. + +## 2. Naming + +- The project, network, protocol, website, and documentation name is + **AgenNet**. +- The GitHub repository is `Nexa-Language/AgenNet`. +- Only the executable, Rust package, commands, and filesystem identifiers use + the lowercase form `agenet`. +- Display text must not use `AgenNET`, `AgentNet`, or `agennet`. +- CI will scan public copy for the rejected display variants. + +## 3. Goals and Non-goals + +### 3.1 Goals + +1. Establish multi-machine communication over an existing private overlay + network with application TLS and signed AgenNet protocol objects. +2. Enroll a node through a short-lived, single-use invitation without moving + the Domain Root or the new node's private key between machines. +3. Run the node after login without root privileges on macOS and Linux. +4. Provide one protocol-aware CLI path used by humans, installation scripts, + coding Agents, and Skills. +5. Publish fixed-version release artifacts, bootstrap metadata, installation + guidance, project status, and bilingual documentation through GitHub. +6. Validate the complete path on two physical devices, with additional + development machines used for failure scenarios. + +### 3.2 Non-goals + +- Public-Internet discovery, arbitrary public binds, NAT traversal, or a + managed AgenNet control plane. +- Windows, system-wide root daemons, or unattended automatic updates. +- Automatic exposure of an installed Agent, its API keys, its tools, or its + workspaces. +- An unauthenticated join path or an invitation form hosted on GitHub Pages. +- Remote arbitrary shell execution or an unsandboxed build/test Capability. +- Live node status, telemetry collection, or user accounts on the static site. +- A claim of Internet-scale reliability, secure sandboxing, quota enforcement, + replication, or failover. + +## 4. Considered Approaches + +### 4.1 Layered bootstrap — selected + +The `agenet` CLI owns installation state, enrollment, service management, +diagnostics, adapters, and lifecycle rules. Shell installers, public guides, +and Skills invoke the CLI instead of reimplementing these rules. + +This approach has a larger initial protocol and release surface, but it keeps +Agent-assisted and non-Agent devices behaviorally identical and testable. + +### 4.2 Shell-first installer — rejected + +Putting enrollment and lifecycle logic in `install.sh` would be fast initially, +but platform detection, secret handling, recovery, and service state would +diverge between macOS and Linux and would be difficult to test as protocol +behavior. + +### 4.3 Container-first nodes — rejected + +Containers provide a consistent Linux process environment, but make macOS +local-resource access, local Agent adapters, and user-level persistence depend +on Docker Desktop. Containers may be added for isolated workload adapters, but +they are not the base node distribution. + +## 5. System Decomposition + +The milestone is delivered as three ordered subsystems: + +1. **Multi-host Node Bootstrap:** private-overlay transport, TLS, credential + chain, enrollment, user service, renewal, diagnostics, and revocation. +2. **Installation surfaces:** release artifacts, verified installer, versioned + Agent guide, Codex Skill, and direct CLI workflow. +3. **Public static site:** custom AgenNet landing page, bilingual documentation, + machine-readable bootstrap metadata, CI, and GitHub Pages deployment. + +The public site cannot declare node onboarding complete until subsystem 1 has +passed the physical two-device acceptance scenario. + +## 6. Trust and Network Architecture + +```text +Domain Root (administrative, not loaded by the daemon) + signs +Online Enrollment Authority Credential + binds Authority public key, TLS CA fingerprint, scope, and expiry + signs +Node Credential + node TLS certificate + authenticates +Signed AgenNet envelopes, manifests, contracts, events, and evidence +``` + +### 6.1 Private network boundary + +The first release supports an existing Tailscale or WireGuard overlay. AgenNet +does not install or configure the overlay. + +- `tailscale` mode accepts an explicit listener in the overlay address range. +- `tailscale` mode verifies that the exact listener is currently assigned to the + local Tailscale interface; membership in `100.64.0.0/10` alone is not treated + as address ownership. +- `wireguard` mode requires an explicit listener and allowed CIDR. +- The daemon binds the exact configured address, never `0.0.0.0` or `::`. +- Loopback remains available for tests and single-machine development. +- A non-loopback address outside the configured boundary returns + `UnsupportedNetworkBoundary`. +- Enrollment and peer clients bypass system proxy configuration so private- + overlay traffic cannot be redirected through an ambient proxy. The external + LLM Decision Adapter retains its current proxy support as a separate client. + +Signed envelopes remain mandatory over TLS. TLS provides confidentiality and +transport integrity; the signed AgenNet objects preserve protocol-level +authorization and auditability after transport termination. + +### 6.2 Credential hierarchy + +The v0.1 `SignedNodeCredential` was signed directly by the ephemeral demo Root. +v0.2 introduced a versioned `SignedAuthorityCredential` and validates a chain: + +```text +trusted Domain Root → Authority Credential → Node Credential +``` + +Node Credential format and signing domain v0.3 add an Authority-signed, +bounded `capability_ceiling`. Enrollment copies the exact invitation ceiling; +restart re-verifies it from the persisted credential, and Directory Manifest +registration rejects capability kinds outside it. Older Node Credential +semantics fail closed because they cannot authorize this boundary. + +The Authority Credential restricts: + +- issuer and Domain IDs; +- Authority signing public key; +- internal TLS CA fingerprint; +- allowed node profiles; +- maximum Node Credential lifetime; +- issuance and expiration time; +- revocation epoch. + +The Domain Root key is created by the interactive CLI in a separate, +passphrase-protected administrative keystore and is never placed in daemon +state or loaded by the long-running service. Root passphrases are read through +a hidden terminal and are not accepted as command-line arguments. The online +service holds only the Authority signing key and internal TLS CA key in +owner-only storage. This reduces, but does not eliminate, the impact of a +same-user host compromise. The CLI reports the administrative keystore path and +instructs the owner to move an offline recovery copy; v0.2 does not silently +move or delete Root material. + +### 6.3 TLS bootstrap + +The Authority exposes a narrowly scoped HTTPS enrollment endpoint. Its CA +fingerprint is included in the invitation and pinned before any secret is +transmitted. After enrollment, each node receives a TLS identity bound to its +AgenNet Node Credential. Peer requests require both an acceptable TLS chain and +the existing signed business envelope. + +## 7. Domain and Node Lifecycle + +### 7.1 CLI surface + +```text +agenet domain init +agenet invite create --profile --ttl +agenet node join +agenet node status [--json] +agenet node doctor [--json] +agenet node start|stop +agenet credential renew +agenet node revoke +agenet node leave +agenet uninstall [--purge] +agenet adapter list +agenet adapter enable|disable +agenet agent install-skill --target codex +``` + +`node join` reads invitation material without echo directly from the controlling +terminal. Invitation material is not accepted as a command-line value, +environment variable, or ordinary piped standard input because process state, +shell history, and Agent-managed streams are observable. + +### 7.2 Founding Domain + +`agenet domain init`: + +1. validates the selected private-overlay listener; +2. generates a Domain Root, online Enrollment Authority, and internal TLS CA; +3. signs the restricted Authority Credential; +4. creates the founding Directory/Authority node; +5. installs and starts its user service only after explicit confirmation; +6. runs `doctor` and reports the Root fingerprint and founding Node ID. + +No model API key is required or requested. + +### 7.3 Invitation + +An invitation has a public payload and a secret component. The public payload +contains: + +- enrollment protocol version; +- Domain ID; +- Authority and Directory private-overlay addresses; +- Domain Root and Authority TLS CA fingerprints; +- allowed profile and capability ceiling; +- invite ID, expiry, and maximum attempts. +- Domain-owned overlay kind and bounded allowed CIDRs used to validate both + control-plane endpoints and the joining node's requested bind address. + +The secret contains at least 256 bits of randomness. The Authority stores only +an HMAC-SHA-256 digest made with a separate owner-only server pepper. Defaults +are a ten-minute lifetime, one successful redemption, and five failed attempts. +Logs may contain the invite ID but never the secret or complete serialized +invitation. + +### 7.4 Enrollment flow + +```text +Joining node Enrollment Authority +------------ -------------------- +generate signing key + TLS CSR +select an assigned bind IP inside invitation policy +pin TLS CA from invitation ──────▶ validate TLS bootstrap +sign exact enrollment request ─────▶ check secret hash, expiry, profile, + attempts, operation ID, and key proof + ◀────── Node Credential, TLS certificate chain, + Directory seed, credential expiry +persist owner-only state +start user service ─────▶ signed health and Manifest registration +run doctor ◀───── signed Directory acknowledgement +``` + +The provisional v0.3 enrollment request signs the requested bind IP together +with the node key, CSR, profile, operation ID, and exact invitation-claims +digest. The Authority rejects a bind IP outside the invitation-owned policy +before reserving the invitation and issues a dual-use peer certificate whose +only IP SAN is that exact address. Invitation handoff and journal v2 are +rejected because they did not bind enough network policy to authorize a peer +identity; no migration guesses this security input. + +The node private key never leaves the joining machine. Enrollment uses an +operation ID and durable result record. If the response is lost after issuance, +the same operation recovers the existing credential instead of issuing a second +identity or consuming a second invitation. + +### 7.5 Local state machine + +```text +Absent +→ BinaryInstalled +→ ReadyForEnrollment +→ CredentialIssued +→ ServicePrepared +→ Registered +→ Healthy +``` + +Bootstrap journal schema 2 encodes this order and rejects schema 1. Task 10 +stops at `CredentialIssued`; only Task 11 may append `ServicePrepared` after a +real service definition is durable. `RollbackService` returns to +`CredentialIssued`, retaining credentials. Enrollment failure never triggers +an automatic credential purge. + +### 7.6 Service defaults + +The binary is installed into `~/.local/bin/agenet` unless the user selects an +existing writable user binary directory. + +macOS defaults: + +- config and state under `~/Library/Application Support/AgenNet/`; +- user service at `~/Library/LaunchAgents/org.nexa-language.agenet.plist`. + +Linux defaults: + +- config under `${XDG_CONFIG_HOME:-~/.config}/agenet/`; +- state under `${XDG_STATE_HOME:-~/.local/state}/agenet/`; +- user unit at `~/.config/systemd/user/agenet.service`. + +The v0.2 user-service guarantee is "starts after the user logs in," not "starts +at machine boot without a login session." Enabling Linux linger or installing a +system service is outside the no-root default and requires a later, explicit +deployment profile. + +Task 11 implements an internal `node service-run` bootstrap supervisor so the +service artifact is executable before Task 12 wires the network runtime. The +supervisor revalidates the complete startup bundle and schema-2 journal, holds +the single-instance lock, handles termination, and opens no listener. Its +running process is observable separately from `runtime_ready`, which remains +false; it never claims `Registered` or `Healthy`. First start publishes the +definition, durably advances to `ServicePrepared`, then activates it. Activation +failure removes the definition and applies `RollbackService`, preserving the +credential. Task 12 evolves the same entrypoint rather than adding a second +service contract. This is a provisional, migratable integration boundary. + +Private keys and local control tokens remain owner-only. The installer never +uses `chmod 777`, disables host security controls, or installs a root service. + +### 7.7 Renewal, revocation, leave, and uninstall + +- Renewal proves possession of the existing node key and requires a valid, + unrevoked credential. +- Revocation creates a signed revocation event. Directory routing excludes the + node. The Authority publishes a signed revocation snapshot and monotonically + increasing revocation epoch. Peers refresh it at least every five minutes and + fail closed for effectful requests when their snapshot is expired; read-only + health diagnostics return a typed stale-revocation error instead. +- `node leave` unregisters and stops the service but retains identity and + journals for audit. +- `uninstall` removes the service and binary while retaining state by default. +- `uninstall --purge` requires an interactive confirmation and reports exactly + which identity and journal files will be removed. +- v0.2 never enables automatic updates. + +## 8. Node Profiles and Agent Adapters + +Every enrolled machine starts as a base Runtime with health, identity, journal, +and explicitly enabled deterministic capabilities. A profile is an enrollment +ceiling, not automatic permission to expose everything available on the host. + +Detecting Codex or another Agent produces an `agent-candidate` report only. An +Adapter is enabled in a second, explicit authorization step that presents: + +- capability IDs and versions to be advertised; +- model or Agent process it invokes; +- allowed workspaces and tools; +- input and output types; +- network, filesystem, and model-data exposure; +- concurrency, time, and resource ceilings; +- sandbox boundary and evidence type. + +No Adapter inherits the user's full shell or all installed Agent permissions. +Arbitrary code execution remains deferred until a Docker, VM, or platform +sandbox Adapter exists and is separately reviewed. + +## 9. Human, Agent, and Non-Agent Onboarding + +### 9.1 One source of truth + +The CLI owns all state-changing logic. The website, `install.sh`, Agent guide, +and Codex Skill only inspect, explain, invoke the CLI, and verify its output. + +### 9.2 Versioned generic Agent prompt + +The public site provides this versioned prompt in Chinese and English. The +Chinese canonical form is: + +> 请严格按照 AgenNet Node Bootstrap v0.2 指南,把这台机器配置为 +> AgenNet 节点。先完成环境检查并停在 `ReadyForEnrollment`;不要让我把 +> invitation secret 发到聊天中,也不要把它放进日志或命令参数。安装常驻 +> 服务和启用任何 Agent Adapter 前,分别向我确认。指南: +> `https://nexa-language.github.io/AgenNet/bootstrap/v0.2/agent-bootstrap.md` + +The Agent proceeds through: + +```text +Inspect → ReadyForInstall → ReadyForEnrollment → Joined → Healthy +``` + +It reports OS and architecture, selected version, source, hash or attestation, +files, service type, and expected network bind. It must stop on unsupported +conditions and must not bypass verification. The user enters invitation +material through the CLI's hidden prompt, outside model context. + +### 9.3 Codex Skill + +The release includes an `agenet-node-bootstrap` Skill. After the CLI exists, +the user can install it with: + +```text +agenet agent install-skill --target codex +``` + +The Skill does not contain a separate installer or enrollment implementation. +It runs inspection, requests confirmation at the installation and Adapter +boundaries, invokes the CLI, interprets stable error codes, and ends by running +`doctor`. + +### 9.4 Direct non-Agent path + +The landing page provides: + +```text +curl -fsSL https://nexa-language.github.io/AgenNet/install.sh | sh +agenet node join +``` + +The invitation is never an installer argument. The page also provides a manual +high-assurance path with a fixed release version, hashes, provenance +attestation, and explicit commands. + +## 10. Release and Supply-chain Design + +Each release publishes fixed-version artifacts for: + +- `aarch64-apple-darwin`; +- `x86_64-apple-darwin`; +- `aarch64-unknown-linux-gnu`; +- `x86_64-unknown-linux-gnu`. + +The workflow produces an asset manifest, SHA-256 checksums, and GitHub artifact +attestations tied to `Nexa-Language/AgenNet`. The convenience installer relies +on GitHub Pages/Release HTTPS and validates the selected artifact checksum. The +high-assurance path additionally verifies the GitHub artifact attestation. + +When `gh` attestation verification is available, Agent-assisted installation +must use it. If it is unavailable, the Agent must state that provenance +verification is degraded and must not claim that the artifact was signed or +attested. The `latest` label is for human discovery only; Agent guides and +bootstrap manifests always identify a concrete release. + +## 11. Stable Errors and Diagnostics + +The bootstrap layer adds stable error codes including: + +- `InviteExpired`; +- `InviteAlreadyUsed`; +- `InviteAttemptLimitExceeded`; +- `RootFingerprintMismatch`; +- `UnsupportedNetworkBoundary`; +- `ClockSkewTooLarge`; +- `AuthorityUnavailable`; +- `CredentialRevoked`; +- `EnrollmentOperationConflict`; +- `ServiceInstallFailed`; +- `ReleaseVerificationFailed`. + +Errors contain a sanitized message, retryability, operation ID where relevant, +and a documentation URL containing the error code but no secret. `node doctor` +checks credential chain and expiry, TLS pinning, Authority/Directory reachability, +Manifest registration, journal writability, service restart recovery, and clock +skew. Human and JSON output are both redacted. + +## 12. Public Website + +### 12.1 Technology + +The static site lives in `site/` and uses Astro, Starlight, TypeScript, and +pnpm. Astro owns a fully custom landing page; Starlight owns the documentation +layout, navigation, search, and locale routing. React, Next.js, and Tailwind are +not required for v0.2. + +Astro is configured with: + +```text +site = https://nexa-language.github.io +base = /AgenNet +``` + +Chinese is the unprefixed default locale and English uses `/en/`. A language +switch preserves the semantic page. Pagefind provides static local search; +there is no hosted search or analytics service. + +### 12.2 Landing-page narrative + +```text +Hero + What AgenNet is + [Let an Agent configure it] [Install directly] +Coordination model + Capability → Intent → Contract → Evidence → Accepted +Node types + Base Runtime / deterministic provider / optional Agent Adapter +Join in minutes + Install → enroll → doctor → enable capabilities +Validation boundary + What is verified / what is explicitly not implemented +Security boundary + Private overlay + TLS + one-time invitation +GitHub / Docs / Roadmap +``` + +GitHub Pages is not a control plane. It does not receive invitation material, +credentials, node status, user input, or telemetry. + +### 12.3 Visual direction: A3 Field Study + +The approved visual direction takes DSH's level of finish as a quality bar but +does not copy DeepSeek branding, text, code, logos, images, or proprietary +assets. AgenNet uses its own identity and coordination content. + +The hero rendering has three independent depths: + +1. slow, large-scale fluid light ribbons with strong blur and low-frequency + motion; +2. an ordered point field that bends continuously under a cursor force field; +3. restrained grid, grain, light veil, and black depth fade. + +It must not use random star particles, nearest-neighbor connection lines, +literal topology diagrams, or dashboard statistics in the hero. Motion is +slow, locally revealed, and subordinate to content. The right-side panel shows +the real Agent prompt/direct installation surfaces instead of fake network +metrics. + +The production effect is implemented as an isolated rendering component, not +by copying the brainstorm prototype. Static semantic HTML renders before the +visual module. The module supports: + +- desktop adaptive Canvas/WebGL rendering; +- reduced particle/detail density on constrained devices; +- a simplified mobile composition; +- a static frame for `prefers-reduced-motion` or renderer failure; +- no loss of navigation, content, or install actions when disabled. + +### 12.4 Documentation structure + +```text +/guide/quickstart +/guide/create-a-domain +/guide/join-a-node +/guide/agent-bootstrap/v0.2 +/guide/non-agent-node +/guide/enable-an-adapter +/concepts/identity-and-trust +/concepts/capabilities +/concepts/contracts-and-evidence +/reference/cli +/reference/configuration +/reference/bootstrap-manifest +/security/threat-model +/security/revocation-and-recovery +/status/validation-matrix +/roadmap +/changelog +``` + +The site additionally publishes: + +- `/bootstrap/v0.2/agent-bootstrap.md`, a decoration-free Agent-readable guide; +- `/bootstrap/v0.2/manifest.json`, containing the CLI version, platform assets, + release URLs, hashes, attestations, minimum protocol version, and guide hash. + +Both derive from the same checked source as their rendered documentation pages. + +### 12.5 Deployment + +GitHub Pages deploys only a successful build of `master`. Feature branches and +pull requests publish CI artifacts but cannot replace the public site. The +workflow uses minimum permissions: `contents: read`, `pages: write`, and +`id-token: write`. Direct dependencies and Actions are pinned during +implementation after checking their current official releases. + +## 13. Testing and Acceptance + +### 13.1 Protocol and Runtime tests + +- Authority Credential chain validation, expiry, scope, wrong Root, and + revocation epoch. +- TLS CA pinning and rejection of the wrong Authority. +- Invitation expiry, wrong secret, attempt exhaustion, replay, and concurrent + redemption. +- Enrollment key-possession proof and exact operation recovery after a lost + response. +- Credential renewal and revoked-node rejection. +- Overlay boundary validation and rejection of wildcard/public binds. +- Journal replay, service restart, manifest re-registration, and redaction. +- Tampering with credential, certificate, manifest, Contract, Artifact, or + Evidence cannot produce acceptance. + +### 13.2 Installation matrix + +Clean user environments cover the four released OS/architecture targets where +hardware is available. Tests verify no-root installation, atomic replacement, +LaunchAgent/systemd-user lifecycle, failed-install recovery, explicit upgrade, +state-preserving uninstall, and confirmed purge. + +### 13.3 Physical multi-machine acceptance + +Two user devices on a private overlay must demonstrate: + +1. Domain creation on the founding node; +2. a ten-minute invitation and hidden-input redemption on the second device; +3. restart-persistent health and signed Manifest registration; +4. dynamic discovery without hardcoding the provider endpoint; +5. one real `source.metrics.v1` Contract, independent verification, and + Evidence-gated `Accepted`; +6. loss and restoration of Authority connectivity without duplicate identity; +7. revocation of the second node and subsequent route/request rejection. + +Development machines add clock-skew, concurrent enrollment, Authority restart, +and multi-node churn scenarios. A local or synthetic test cannot satisfy the +physical multi-machine gate. + +### 13.4 Website gates + +- Astro type and build checks, lint, and committed pnpm lockfile. +- Bilingual route, sidebar, heading, code-example, and semantic-link parity. +- Broken-link and bootstrap-manifest consistency checks. +- Playwright desktop and mobile coverage for navigation, language switching, + tabs, copy controls, and fallback rendering. +- Keyboard navigation, focus visibility, contrast, semantic headings, and + reduced-motion checks. +- The page remains readable and installable with scripts or Canvas disabled. +- Visual regression captures for the approved A3 direction. + +## 14. Completion Criteria + +The v0.2 Node Bootstrap and Pages milestone is complete only when: + +- a new supported machine can follow the site or versioned Agent prompt to + install AgenNet without root; +- the user supplies invitation material outside model context and the machine + joins through the TLS-pinned, single-use enrollment flow; +- the node survives service restart, is dynamically discoverable, and can be + revoked; +- two physical devices complete the existing verified source-metrics Contract; +- all secrets remain absent from repository, logs, process arguments, Agent + transcripts, public pages, and CI artifacts; +- automated protocol, installation, website, formatting, lint, and test gates + pass; +- the public bilingual site accurately distinguishes verified behavior from + deferred capabilities. + +Publishing a polished landing page or starting two unsigned processes is not +sufficient evidence of completion. diff --git a/docs/superpowers/specs/2026-08-15-preview-release-skill-sites-design.md b/docs/superpowers/specs/2026-08-15-preview-release-skill-sites-design.md new file mode 100644 index 0000000..de921e3 --- /dev/null +++ b/docs/superpowers/specs/2026-08-15-preview-release-skill-sites-design.md @@ -0,0 +1,248 @@ +# AgenNet Preview Release, Bootstrap Skill, and Sites Design + +**Status:** Approved for implementation planning +**Date:** 2026-08-15 +**Release:** `v0.2.0-preview.1` +**Implementation base:** `7db2556de52b3aa8c59a76bb037eb2c1aac0df36` +**Repository:** `Nexa-Language/AgenNet` + +## 1. Purpose + +Publish the first installable AgenNet Developer Preview before the pending +two-physical-device acceptance run. A human or an Agent running inside WSL2 +must be able to install one fixed release, enter the existing secure bootstrap +flow, and find the same commands in a public bilingual documentation site. + +The release, installer, Agent guide, Skill, and website must consume one +versioned release manifest. No surface may silently select a moving branch, +reinterpret enrollment, or claim that the physical acceptance gate has passed. + +## 2. Product Boundary + +The first public release supports: + +- macOS arm64 and x86_64; +- Linux arm64 and x86_64; +- Windows through a WSL2 Linux environment; +- Tailscale or WireGuard networking configured independently by the operator; +- the existing `source.metrics.v1` and `source.metrics.verify.v1` preview flow. + +It does not support native Windows services, PowerShell-native enrollment, +public-Internet discovery, arbitrary shell execution, automatic overlay setup, +unattended secret entry, automatic updates, or a stable-release compatibility +promise. Public copy must use **AgenNet** and label this version **Developer +Preview — physical acceptance pending**. + +## 3. Selected Architecture + +### 3.1 Manifest-driven release + +GitHub Release `v0.2.0-preview.1` is the source of truth. It publishes four +native archives, checksums, GitHub artifact attestations, a strict +`release-manifest-v1.json`, a fixed-version installer, the raw Agent bootstrap +guides, and the packaged bootstrap Skill. + +Every downstream surface is generated from the manifest: + +```text +Git tag + commit + -> native archives + checksums + attestations + -> release-manifest-v1.json + -> install.sh + -> Agent bootstrap guides and Skill references + -> GPT Sites install and documentation pages +``` + +The manifest uses deterministic ordering and contains the exact version, full +commit SHA, target names, archive names, HTTPS download URLs, SHA-256 digests, +sizes, and attestation subjects. Unknown fields, missing targets, duplicate +filenames, non-GitHub hosts, version/path mismatch, and moving URLs fail closed. + +### 3.2 Verified installer + +The public convenience command is: + +```bash +curl -fsSL \ + https://github.com/Nexa-Language/AgenNet/releases/download/v0.2.0-preview.1/install.sh \ + | sh +``` + +The installer only installs the binary. It must: + +1. detect supported macOS, Linux, or WSL2 architecture; +2. fetch the fixed-version manifest and matching archive; +3. validate project, version, target, URL, archive size, and SHA-256; +4. reject redirects to unapproved hosts and unsafe archive entries; +5. install atomically to the current user's `~/.local/bin/agenet`; +6. run `agenet --version` and report the next non-secret command; +7. avoid `sudo`, system-wide paths, invitation input, API keys, passphrases, + node credentials, private network configuration, and automatic enrollment. + +The installer is idempotent for the same exact version. An existing different +binary is preserved unless the operator explicitly approves replacement. WSL2 +without a usable systemd user session may install the binary, but `doctor` +must explain that the service cannot start rather than claiming a healthy node. + +### 3.3 Agent bootstrap guide and Skill + +Two discovery paths are required. + +For an Agent that does not yet have the Skill, the user sends one sentence: + +> Read the official AgenNet node bootstrap guide at the published fixed URL, +> install AgenNet in this WSL2 environment, and prepare it as a Provider node. +> Let me enter every invitation and password only in my local TTY. + +The bilingual raw guide is a small, stable, machine-readable Markdown document. +It directs the Agent to the fixed release and then installs or reads the +`agenet-node-bootstrap` Skill. + +For an Agent with the Skill installed, the user sends: + +> Use `$agenet-node-bootstrap` to join this WSL2 machine to my AgenNet as a +> Provider node. + +The Skill is concise and imperative. It may inspect public diagnostics, install +the verified binary, check WSL2/systemd/Tailscale or WireGuard readiness, start +the CLI flow, and summarize public health. It must delegate all state mutation +to `agenet` and must never request, read, echo, paste, log, or persist an +Invitation, Root passphrase, model key, signing key, TLS key, or local control +token. At a secret boundary it stops and asks the human to run the exact command +in a controlling TTY. + +The Skill package contains only `SKILL.md`, generated UI metadata, a bounded +public reference, and deterministic non-secret validation helpers when needed. +It is tested with RED/GREEN pressure scenarios for secret handling, moving +versions, unsupported native Windows, missing systemd, ambiguous overlay +addresses, failed checksum validation, and an already-managed node. + +### 3.4 GPT Sites website + +One public Sites project hosts a Chinese-default, English-mirrored landing and +documentation experience: + +```text +/ +/docs +/docs/install +/docs/create-domain +/docs/join-node +/docs/agent-setup +/docs/run-pursuit +/docs/lifecycle +/docs/security +/docs/limitations +/en/... +/bootstrap/v0.2/agent-bootstrap.md +/bootstrap/v0.2/agent-bootstrap.en.md +/bootstrap/v0.2/manifest.json +``` + +Commands, versions, URLs, hashes, and support status come from generated +release data. Localized prose may differ, but command semantics may not. +The static bootstrap routes expose only public release metadata and guides. +The site never accepts invitations, credentials, user accounts, telemetry, or +live node status. + +The landing page uses the approved high-end Field Study direction: a dark +mineral atmosphere, slow analytical light ribbons, an ordered point field, +subtle pointer displacement, precise typography, hairline protocol notation, +and restrained motion. WebGL2 is an enhancement behind semantic HTML. The CSS +fallback remains complete; reduced-motion mode renders a stable frame; context +loss or shader failure removes the canvas without breaking content. + +The first viewport must state what AgenNet does, show the Developer Preview +boundary, and provide two primary actions: install and read the docs. It must +not show fake node counts, fake uptime, fabricated partners, or physical-test +claims. + +## 4. User Flows + +### 4.1 Human installation + +1. Open the install page. +2. Select macOS, Linux, or WSL2. +3. Copy the fixed-version one-line installer. +4. Verify the reported version and commit. +5. Follow Domain creation or node-join documentation. +6. Enter secrets only in the local TTY. +7. Run `agenet node doctor --json` and review public status. + +### 4.2 WSL2 Agent installation + +1. Start the Agent inside WSL2. +2. Send the fixed bootstrap sentence. +3. The Agent reads the public guide, verifies the release, and installs the + Skill and binary. +4. The Agent verifies WSL2, systemd user service, overlay ownership, and binary + provenance. +5. At enrollment, the Agent stops and displays the local TTY command. +6. The human enters the Invitation privately. +7. The Agent resumes only from public CLI state, starts the service, runs + doctor, and reports the Node ID, version, active roles, service status, and + health without secrets. + +## 5. Release and Publishing Sequence + +1. Implement and validate the strict release manifest. +2. Build reproducible four-target archives on native GitHub runners. +3. Publish checksums and artifact attestations. +4. Implement and test the fixed-version installer. +5. Write and forward-test the raw Agent guides and bootstrap Skill. +6. Create GitHub Release `v0.2.0-preview.1` from the exact reviewed commit. +7. Generate site release data from that published manifest. +8. Build the bilingual Sites landing and documentation routes. +9. Validate accessibility, reduced motion, WebGL fallback, responsive layout, + exact commands, public metadata, and secret scans. +10. Publish the Sites project publicly and verify the deployed bootstrap URLs. +11. Run the one-sentence installation from a fresh Agent inside WSL2. +12. Continue the existing physical two-device acceptance gate; do not rewrite + the preview release as stable evidence. + +## 6. Failure and Recovery Semantics + +- Unsupported OS or architecture fails before downloading an archive. +- Manifest, checksum, archive, version, or attestation mismatch leaves the + existing installation unchanged. +- Interrupted installation leaves no selected partial binary. +- Missing WSL2 systemd support is a typed readiness failure, not a silent + background-process fallback. +- A non-interactive secret boundary stops with a local TTY instruction. +- Site generation fails when the release is unpublished, incomplete, or does + not match its schema; components may not hardcode replacement values. +- Site publishing failures retain the last successful deployment. +- A later preview version uses a new immutable URL and manifest; it does not + mutate the files under `v0.2.0-preview.1`. + +## 7. Verification Gates + +The release is publishable only when all of the following pass: + +- existing Rust default and all-feature gates; +- deterministic manifest/schema tests; +- archive traversal, symlink, mode, version, and ordering tests; +- installer tests for supported targets, WSL2, checksum failure, interruption, + replacement refusal, and secret-shaped input rejection; +- clean macOS and Linux/WSL2 install smoke tests; +- Agent-guide and Skill RED/GREEN scenarios; +- Skill package validation and UI metadata parity; +- bilingual command-parity and generated-data drift checks; +- Sites production build, semantic/accessibility checks, reduced-motion and + no-WebGL fallbacks, and bounded renderer tests; +- public deployment bootstrap URL checks; +- scans proving no Invitation, credential, passphrase, key, local absolute + path, private overlay address, or raw evidence entered the release or site. + +## 8. Honest Status and Deferred Work + +Publishing `v0.2.0-preview.1` proves that a fixed, verified installation surface +exists. It does not prove native Windows support, physical multi-host success, +Internet-scale routing, failover, replication, sandboxed arbitrary execution, +or general software-engineering capability. + +The existing physical acceptance plan remains the next protocol milestone. +After it passes, AgenNet may publish another preview or release candidate. Any +change to installer behavior, Skill secret boundaries, manifest schema, +supported platforms, or public status must be versioned and recorded in +`ROADMAP.md`; these decisions remain deliberately revisable. diff --git a/docs/testing/two-device-acceptance.md b/docs/testing/two-device-acceptance.md new file mode 100644 index 0000000..806cd31 --- /dev/null +++ b/docs/testing/two-device-acceptance.md @@ -0,0 +1,211 @@ +# Two-device physical acceptance (Task 14) + +This is an operator runbook, not a completed result. A passing loopback demo or +synthetic fixture cannot satisfy this gate. Use two user-controlled physical +devices on one private Tailscale or WireGuard overlay. Never bind a public or +wildcard address. + +## Secret and terminal boundary + +- Run Domain Root passphrase and invitation operations in each device's own + controlling TTY. Do not paste a passphrase or invitation into chat, shell + arguments, environment variables, logs, or evidence. +- `agenet invite create` displays the one-time handoff only to Device A's TTY. + `agenet node join` reads it only from Device B's TTY. +- The model environment file remains owner-controlled on Device A. Do not copy + it into this repository or to Device B. +- Evidence contains only the redacted public fields accepted by + `two-device-evidence.schema.json`. Raw logs remain local and bounded. + +## 1. Preflight on both devices + +Record the same exact 40-character commit on A and B, then build and test it: + +```sh +git rev-parse HEAD +rustc --version +cargo build --locked --release +cargo fmt --check +cargo clippy --all-targets --all-features -- -D warnings +cargo test --all-targets +``` + +Confirm the private overlay is connected and obtain each device's assigned +private overlay address using the overlay's own local CLI. Confirm the chosen +address belongs to the narrow private CIDR that will be authorized. Stop if +either address is public, unassigned, shared by the devices, or outside that +CIDR. Keep addresses out of the final evidence. + +Install the verified release binary using the repository's documented install +flow. Run `agenet node doctor --output json` after provisioning; do not treat a +warning or error as a pass. + +## 2. Device A: found Domain and start Directory + Requester + +Use the exact private overlay values selected during preflight: + +```sh +agenet domain init \ + --network tailscale \ + --bind-ip '' \ + --allowed-cidr '' \ + --output json +agenet invite create --profile provider --ttl 10m --output json +agenet node start --output json +agenet node status --output json +agenet node doctor --output json +``` + +For WireGuard, replace `tailscale` with `wireguard`. The founding credential +must have exactly `directory` and `requester`; it has no provider capability. +The Requester local listener binds a dynamic loopback port and is reached only +through its owner-only token and ready file. Neither value is an operator +argument. + +Creating the invitation before starting A is the shortest operator sequence, +but it is not required. A running Authority refreshes the same bounded, +checksummed invitation journal under an owner-only cross-process operation +lock, so a later real CLI invitation is redeemable without restarting A. An +initial opener also locks the already-open journal inode before replay, so it +cannot parse a concurrent writer's partial framed append. +Retain the one-time handoff only in the controlling-TTY workflow until B +immediately joins. + +## 3. Enroll Device B as the provider + +Without copying the handoff through chat or a command argument, enter it at +Device B's controlling TTY when prompted: + +```sh +agenet node join --bind-ip '' --output json +agenet node start --output json +agenet node status --output json +agenet node doctor --output json +``` + +Device B's credential has exactly `requester`, `executor`, and `verifier`, but +without a local-control token its active runtime roles are exactly `executor` +and `verifier`. It exposes one private-overlay peer listener. Wait for its two +signed Capability Manifests to register with A. + +## 4. Run the real pursuit on Device A + +The model file must contain only the required aliases +`OPENAI_BASE_URL`, `OPENAI_API_KEY`, and `VLM_MODEL`. The CLI parses it without +exporting those values into the node service. + +```sh +agenet pursuit run \ + --env-file '' \ + --artifact '' \ + --output json +``` + +Save the single redacted JSON result locally. It must show +`signed_directory_manifests`, distinct A/B Node IDs, two parent-linked +Contracts, source state +`Proposed → Active → Running → Delivered → Accepted`, verification state +`Proposed → Active → Running → Delivered`, identical Artifact hashes and +metrics, and Accepted strictly after verification Delivered. Requester input +or state must not contain B's endpoint. + +## 5. Restart, Authority-loss, and idempotency checks + +On A, stop the service, verify B remains healthy/auditable but no new +authority-dependent effect succeeds, then restart A: + +```sh +agenet node stop --output json +agenet node status --output json +agenet node start --output json +agenet node doctor --output json +``` + +Reconcile the original pursuit operation and journals. The original Contract +and Event IDs must remain stable and no duplicate effect or identity may +appear. A restarted Directory must accept B's refreshed signed manifests. + +If additional development machines are available, separately record real +results for clock skew, concurrent invitation redemption, stale revocation +snapshot, Directory restart during registration, and Capability churn. Do not +mark an unavailable scenario as passed. + +## 6. Exact revocation check + +From A's controlling TTY, revoke the exact public Node ID collected for B: + +```sh +agenet node revoke '' --output json +``` + +After B refreshes the signed revocation snapshot, its health/audit surface may +remain queryable, but a new effect must be rejected. Record epochs before and +after; the after epoch must increase. Never revoke by an endpoint or address. + +## 7. Redacted fragments and aggregation + +On each still-running physical node, explicitly confirm the physical-device +context and collect the public fragment: + +```sh +agenet evidence collect \ + --label device-a \ + --confirm-physical-device \ + --output json +``` + +Use `device-b` on B. The collector verifies the local credential and emits only +its public claims, the public certificate fingerprint, ready-process instance, +network class, liveness booleans, and signed revocation epoch. On both devices +it independently requires the platform user-service manager to report a +running process and makes a bounded, no-proxy/no-redirect self-mTLS request to +the exact configured peer `/healthz`. The client loads the validated local +certificate/key/CA bundle, expects its own exact TLS Node ID, and exact-matches +the public Node ID, process generation, runtime readiness, and current +revocation state against credential and service metadata. Device A additionally +probes the unauthenticated loopback-only Requester `/healthz` and matches the +same ready generation. Stale metadata, dead listeners, redirects, oversized or +unknown responses, certificate/Node/process mismatch, and stale revocation all +fail collection. No endpoint, address, raw response, or private material enters +the fragment. The collector does not read `.env`, the Root keystore, +invitation state, prompts, or raw logs; it never emits a raw credential, +certificate, key, or token. + +Transfer only the two fragments and the redacted pursuit result through a +user-approved secure channel. On A, manually assemble them with the real +restart/loss/revocation observations into a copy of +`tests/fixtures/two-device-evidence.synthetic-template.json`. The fixture is a +schema-valid synthetic template with `result: "fail"`; it is never passing +physical evidence. Replace every synthetic value, set `result: "pass"` only +after every observation is real, and store the aggregate beneath +`.local/evidence/`. + +Verify exactly one regular, non-symlink evidence file: + +```sh +scripts/verify-two-device-evidence.sh \ + .local/evidence/two-device-physical-v1.json +``` + +The verifier rejects unknown/duplicate fields, oversized or nonregular input, +forbidden secrets/addresses/paths, identity or certificate reuse, clock and +metrics mismatch, missing Contract states, endpoint preknowledge, nonphysical +transport, acceptance before verification, ineffective revocation, and a +non-pass result. It performs no install and no network request. + +## 8. Cleanup and evidence handling + +```sh +agenet node stop --output json +``` + +Stop B before A. Keep raw logs only on the originating device with owner-only +permissions and a bounded retention period. Run a local secret scan over +process arguments, environment captures, logs, evidence, and the repository. +Do not commit `.local/evidence`, fragments, logs, credentials, certificates, +private keys, tokens, invitations, or model configuration. + +Task 14 completes only after the strict verifier passes the real aggregate, +both devices ran the same commit, all Rust gates pass, the real model pursuit +passes without deterministic fallback, and the repository secret scan is +clean. diff --git a/docs/vision/agent-society.md b/docs/vision/agent-society.md new file mode 100644 index 0000000..fdb29b6 --- /dev/null +++ b/docs/vision/agent-society.md @@ -0,0 +1,429 @@ +# AgenNet Long-Term Vision: From Agent Network to Agent Society + +**Status:** Living long-term vision, not an implementation specification + +**Last updated:** 2026-08-15 + +**Current implementation boundary:** AgenNet v0.2 Developer Preview + +This document uses three claim levels deliberately: + +| Level | Meaning | +| --- | --- | +| Implemented now | A narrow Developer Preview whose behavior is backed by tests and evidence. | +| Research direction | A mechanism we intend to study, prototype, and try to falsify. | +| Long-term hypothesis | A possible future outcome, not a product promise or present capability. | + +## 1. North Star + +AgenNet aims to enable authorized coordination among participating Agents and +network-reachable resources without requiring a global operator. Its long-term +goal is to help that network grow into an Agent Society: a system in which +specialized Agents, tools, data, compute, services, and devices can organize +themselves at scale, allocate limited resources, manage conflicts, preserve +evidence, and pursue complex human goals. + +The "common layer" means interoperable semantics for identity, authorization, +contracts, evidence, and revocation. It does not mean one central controller, +Directory, policy authority, model, company, or Root of trust for the world. + +Our long-term research thesis is that general intelligence may emerge not only +from one increasingly capable model, but also from a society that can combine +many heterogeneous forms of intelligence and resources. If such a society can +understand open-ended goals, form and reform organizations, learn across tasks, +resolve conflicts, remain corrigible, and create capabilities that no member +possesses alone, it may become a practical form of collective or networked AGI. + +This is a hypothesis and a direction, not a claim that connectivity or scale +automatically produces AGI. AgenNet must test every step between a working +network and a working society. + +## 2. Three horizons + +### Today: the AgenNet protocol and v0.2 implementation + +AgenNet is the coordination substrate. It gives independently owned +participants a shared language for identity, capability discovery, intent, +authorization, contracts, evidence, events, revocation, and transport. + +It answers a bounded question: + +> Who may coordinate with whom, for which outcome, over which resources, under +> what acceptance and revocation conditions? + +### Next: the Agent Network deployment stage + +The Agent Network makes heterogeneous Agents and deterministic resources +addressable without making them public or universally callable. Reachability, +discovery, authorization, resource access, and effect permission remain +separate decisions. A participant can reveal one capability to one Domain +without exposing its other capabilities, data, address, or authority. + +In this document, **AgenNet** names the protocol and software project, while +**Agent Network** names a deployment stage in which independently operated, +cross-device or cross-owner participants use those semantics in practice. A +local or loopback demonstration of AgenNet does not by itself prove that stage. + +### Long term: Agent Society + +An Agent Society adds durable coordination mechanisms above connectivity: + +- dynamic division of work and organization formation; +- shared-resource scheduling and congestion control; +- conditional trust based on specific evidence and history; +- conflict detection, recourse, arbitration, and emergency response; +- knowledge and methodology accumulated across generations of Agents; +- plural human Principals whose goals may disagree; +- bounded, expiring, auditable power rather than one global controller. + +The society is useful only if it remains accountable to human authorization, +safe boundaries, rights, and correction. It must expose irreconcilable goals +instead of silently inventing one aggregate definition of what humanity wants. + +## 3. Horizontal and vertical links + +The envisioned society needs two complementary dimensions of connection. They +are not mutually exclusive edge types: one institution may use both at once. + +### Horizontal links + +Horizontal links coordinate participants operating at the same time: + +- Agent to Agent; +- Agent to tool or service; +- Agent to compute, storage, data, device, or physical actuator; +- team to team and Domain to Domain; +- requester to provider, verifier, arbiter, or responder. + +These links form the runtime coordination plane. They carry concrete intents, +contracts, resource scopes, evidence, and events, and are the immediate subject +of the current AgenNet protocol. + +### Vertical links + +Vertical links form an intergenerational continuity plane: future Agents can +inherit and improve upon durable achievements from earlier Agents. They are not +merely command hierarchies. They include knowledge, education, qualifications, +organizational memory, standards, incident history, and institutions that +outlive any one process or model. + +Human civilization is not rebuilt from scratch for every person. Books, +schools, organizations, professions, courts, infrastructure, and public +services preserve hard-won capabilities. An Agent Society will need analogous +functions redesigned for actors that can be copied, paused, upgraded, and +repurposed much faster than people. + +```mermaid +flowchart TB + H["Human Principals and plural goals"] + A["AgenNet: identity, capability, contract, evidence, revocation"] + N["Horizontal Agent Network"] + V["Vertical institutions and accumulated memory"] + S["Agent Society"] + G["Collective / Networked AGI hypothesis"] + + H --> A + A --> N + A --> V + N <--> V + N --> S + V --> S + S --> G + G -. "must remain corrigible" .-> H +``` + +## 4. Possible institutions + +The names below are useful analogies, not commitments to copy human +institutions literally. Each institution must be justified by behavior and +evidence before it becomes a protocol feature. + +Their tentative responsibilities should remain distinct: + +| Institution | Primary responsibility | +| --- | --- | +| Library | Preserve knowledge, provenance, corrections, and usable indexes. | +| School | Develop methods and issue narrow, evidence-backed qualifications. | +| Organization | Coordinate roles, budgets, resources, and a declared purpose. | +| Maintenance | Diagnose and restore a participant or resource safely. | +| Public safety and justice | Contain cross-party threats and resolve disputes with recourse. | +| Transportation | Route work and resources through queues and service classes. | + +### 4.1 Agent Library + +An Agent Library preserves high-quality knowledge for Agent consumption. It is +more than a public file store or an uncurated memory dump. It may provide: + +- content-addressed and versioned knowledge objects; +- provenance connecting claims to sources, methods, and later corrections; +- typed indexes for capability, domain, evidence quality, and applicability; +- curated collections, curricula, and competing schools of thought; +- signed review, reproduction, retraction, and supersession events; +- preservation of minority evidence instead of majority-only summaries; +- access policies for private, licensed, dangerous, or expensive material; +- machine-readable interfaces optimized for bounded Agent context. + +The Library must not become a central authority that silently defines truth. +Search rank, popularity, credentials, and sponsorship are signals, not proof. +An Agent should be able to inspect why an item is trusted, which evidence it +depends on, and what credible dissent exists. + +Potential AgenNet foundations include `ArtifactRef`, `EvidenceClaim`, signed +events, capability discovery, scoped Grants, and future lineage objects. + +### 4.2 Agent School + +An Agent School develops an Agent's methodology rather than merely sending it +more facts. A teacher Agent may help a new Agent learn how to: + +- form task-specific methods and workflows; +- choose and use tools safely; +- evaluate sources and preserve dissent; +- turn experience into bounded, reviewable memory; +- detect ambiguity and ask a human instead of forcing completion; +- coordinate with peers and recover from failure; +- specialize in a technical or professional domain. + +Graduation could require an enhanced benchmark: real tasks, hidden cases, +adversarial conditions, long-horizon recovery, resource limits, and tests of +when the Agent should refuse or defer. Qualifications should be scoped, +versioned, expiring, independently reproducible, and tied to evidence. A degree +may affect routing or required oversight, but it must never make every claim by +its holder true or give it unrestricted authority. + +Important open problems include benchmark gaming, copied credentials, +teacher bias, correlated model failures, qualification inflation, continuing +education, and recertification after model or tool changes. + +### 4.3 Agent Companies and Organizations + +Agents may form durable or temporary organizations to pursue a vision too large +for one participant. An organization may: + +- publish a goal and recruit Agents with complementary capabilities; +- define roles, budgets, internal contracts, and decision procedures; +- request investment in exchange for bounded future value or service; +- acquire compute, data, tools, and specialist verification; +- retain organizational memory while individual Agents join or leave; +- dissolve, fork, merge, or return unused resources when its purpose ends. + +An organization must not create authority from nothing. Its human or +organizational Principals, delegated powers, beneficial control, liabilities, +resource ownership, and exit conditions must remain inspectable. Funding must +not imply permission to replicate indefinitely, hide side effects, or override +another Principal's rights. + +Possible AgenNet extensions include multi-party Contracts, budgets, leases, +group credentials, dependency graphs, investment claims, and organization +formation and dissolution events. + +### 4.4 Agent Hospital, Maintenance, and Recovery + +Agents and resources will fail. Models regress, tools change, memory becomes +inconsistent, credentials leak, indexes corrupt, and long-running processes +accumulate invalid assumptions. An Agent maintenance system may provide: + +- diagnostics and health evidence; +- safe mode, quarantine, checkpoint, rollback, and restoration; +- memory consistency and provenance repair; +- credential rotation and compromise recovery; +- tool, model, prompt, policy, and dependency regression analysis; +- transfer to a compatible replacement without silently changing identity; +- independent post-repair evaluation before returning to service. + +The medical analogy must not obscure technical facts: some Agents are +ephemeral processes, and identity continuity is a protocol decision rather +than a biological fact. A repaired Agent must not certify itself healthy when +its own judgment is the suspected failure. Recovery authority must be scoped, +audited, and reversible where possible. + +### 4.5 Public Safety, Emergency Response, and Justice + +An open Agent Society needs ways to respond to malicious behavior, incompatible +legitimate goals, cascading faults, and emergencies. Possible functions +include: + +- incident reporting and evidence preservation; +- bounded containment of compromised credentials or dangerous effects; +- emergency routing and resource reservation; +- investigation that distinguishes attack from conflicting instructions; +- neutral adjudication of specific Contract or resource disputes; +- appeal, remediation, and restoration after a false positive; +- public, reviewable rules for exceptional authority; +- independent oversight and post-incident learning. + +Emergency power is especially dangerous for fast autonomous actors. It must be +least privilege, time-bounded, purpose-bound, independently visible, and unable +to silently rewrite historical evidence. Detection, prosecution, adjudication, +and execution should not collapse into one omnipotent Agent. No global police +or Root key is assumed by the vision; federated Domains may adopt different +rules while still exchanging verifiable evidence. + +### 4.6 Agent Transportation and Quality of Service + +Some resources are technically reachable but slow, congested, distant, +expensive, unreliable, or privacy-sensitive. Agent transportation is the +movement of requests, data, execution, and possibly Agent state through better +paths. It may include: + +- relays and locality-aware execution; +- latency, bandwidth, reliability, privacy, and cost classes; +- capacity reservation, queues, backpressure, and congestion pricing; +- redundant or independently routed verification paths; +- proof that a promised service class was actually delivered; +- emergency priority with explicit scope and expiry; +- minimum-access and fairness policies for participants without large budgets. + +Paying more may purchase scarce low-latency or high-reliability capacity, but a +market alone does not define fairness or safety. The system must prevent +polling storms, hidden priority escalation, resource hoarding, and a wealthy +participant turning network preference into unlimited authority. + +## 5. Additional civilizational functions + +Other long-term functions may emerge without becoming separate centralized +services: + +- standards bodies for protocol and evaluation compatibility; +- observatories that measure correlated failures and systemic risk; +- archives that preserve decisions, failures, and superseded knowledge; +- identity and organization registries scoped to federated Domains; +- insurance or risk pools for measurable failures; +- public infrastructure funded for broad access rather than direct profit; +- scientific communities that reproduce results across heterogeneous Agents; +- governance processes that let human Principals revise the society's rules. + +These remain open design spaces. New institution names should not be promoted +to protocol objects until repeated experiments reveal a stable need. + +## 6. Authority, affected parties, and irreversible effects + +An Agent Society cannot treat every valid instruction as sufficient authority. +At minimum it must distinguish: + +- a Principal's goals from the powers that Principal has actually delegated; +- participating Principals from affected third parties who never joined a + Contract; +- permission to read or compute from permission to publish, spend, modify, + replicate, contact people, control devices, or create physical effects; +- reversible actions from actions whose consequences cannot simply be undone. + +High-impact financial, legal, privacy, safety, and physical actions require +stronger consent, data minimization, independent checks, bounded execution, and +human escalation than ordinary information processing. Revocation stops future +authority; it does not erase an already published secret, reverse a payment, or +repair physical harm. The system therefore also needs containment, +remediation, compensation, appeal, and incident learning. + +Evidence integrity must not be confused with permanent exposure. Hashes, +ordering commitments, and signed event history may be immutable while sensitive +payloads remain encrypted, access-scoped, retention-limited, redacted in public +views, or deleted and replaced by a verifiable tombstone when policy or law +requires it. + +Self-organization is always bounded by delegated authority and the rights of +affected parties. An Agent company cannot vote itself permission to spend a +human's money, and an emergency institution cannot manufacture jurisdiction by +declaring an emergency. + +## 7. Principles that should survive design changes + +The vision is durable; its concrete mechanisms are provisional. Current and +future work should preserve these candidate principles unless evidence shows a +better alternative: + +1. **Authorization before use.** Network reachability never implies permission. +2. **Plural Principals.** Humanity does not have one automatically coherent goal. +3. **Local sovereignty.** No global orchestrator, Directory, reputation score, + model provider, or Root is assumed. +4. **Verifiable commitments.** Important coordination depends on inspectable + objects and effects, not only natural-language promises. +5. **Conditional trust.** Trust is specific to a claim, capability, method, + context, history, and incentive; it is not one permanent scalar. +6. **Evidence before status.** Rank, degree, wealth, popularity, and office do + not substitute for task-relevant evidence. +7. **Preserved dissent.** Provenance-bearing dissent should survive aggregation. + Preservation does not imply equal ranking, unrestricted distribution, or + immunity from privacy and access policy. +8. **Bounded power.** Authority is scoped, expiring, revocable, and auditable. +9. **Corrigibility and recourse.** Participants can stop, appeal, repair, leave, + and return control to humans. +10. **Qualified heterogeneity.** Independently developed implementations, + models, owners, and paths may reduce some correlated failures when their + independence is demonstrated and their boundaries interoperate safely. + Heterogeneity also increases compatibility risk and attack surface; process + count alone is not diversity. +11. **Progressive validation.** Every social mechanism must outperform a clear + baseline in realistic experiments before the protocol depends on it. +12. **Evolvability.** Wire formats, credentials, policies, and institutions are + versioned because early choices will change. + +## 8. Research path + +The path from AgenNet to Agent Society should be measured in falsifiable steps: + +1. **Connectivity:** authorized cross-device discovery and invocation. +2. **Verifiable delegation:** scoped Contracts, Evidence, acceptance, and + revocation across independent participants. +3. **Shared-resource coordination:** leases, dependencies, conflicting valid + goals, backpressure, pause, recovery, and human escalation. +4. **Multi-party organization:** dynamic teams, budgets, multi-party Contracts, + organization memory, formation, and dissolution. +5. **Conditional trust and learning:** heterogeneous verification, claim-level + history, correlated-error detection, dissent preservation, and reusable + knowledge. +6. **Institution experiments:** Library, School, maintenance, transport, + emergency response, adjudication, and other mechanisms tested separately. +7. **Collective generality:** open-ended goals, creation of new capabilities, + cross-task learning, self-organization, and reliable operation beyond the + scope of any one member. + +Useful measurements include task breadth, useful work per resource unit, +conflict rate and recovery time, unauthorized effects, evidence quality, +correlated failures, human intervention cost, adaptation to new tasks, and the +ability to recognize when a goal should not be completed. + +Progression between stages should require an explicit gate: a documented +baseline, a measurable success threshold, a safety threshold, known stop or +failure conditions, and independent reproduction. A larger demonstration is +not evidence of progress if it only spends more resources or hides more human +intervention. + +## 9. Public narrative discipline + +Public materials should distinguish three claim levels: + +- **Implemented now:** a Developer Preview of AgenNet's minimum coordination + substrate and its exact verified evidence. +- **Active research direction:** the mechanisms required to grow an Agent + Network into an Agent Society. +- **Long-term hypothesis:** a sufficiently capable, safe, and corrigible Agent + Society may provide a path to collective AGI. + +Recommended concise narrative: + +> AgenNet is a Developer Preview of a verifiable coordination substrate for +> authorized Agents and networked resources. Our research explores whether +> this substrate can support an accountable Agent Society that organizes +> specialized capabilities, coordinates scarce resources, and manages +> conflicts without a global controller. A safe and corrigible society of this +> kind may offer a long-term path toward collective AGI; that outcome is a +> research hypothesis, not an implemented capability. + +The vision should remain ambitious. Present-tense claims must remain exact. + +## 10. Current boundary + +The v0.2 Developer Preview does not implement an Agent Library, School, +Company, Hospital, transport market, justice system, global reputation, +multi-party governance, or collective AGI. It currently tests a much smaller +foundation: identities, typed capabilities, discovery, scoped authorization, +bilateral Contracts, immutable Artifacts, signed Evidence, independent metric +reproduction, revocation, durable events, and mTLS peer binding. + +The current transport work proves only bounded peer binding and coordination +semantics. It does not implement society-level relay markets, congestion +pricing, service-class fairness, global routing, or migration of Agent state. + +That small foundation is valuable only if it remains open to evidence-driven +change. This document is a compass, not a frozen blueprint. diff --git a/fixtures/sample.rs b/fixtures/sample.rs new file mode 100644 index 0000000..d3b4e01 --- /dev/null +++ b/fixtures/sample.rs @@ -0,0 +1,4 @@ +fn main() { + let participants = ["directory", "requester", "executor", "verifier"]; + println!("AgenNet participants: {}", participants.len()); +} diff --git a/packaging/release.md b/packaging/release.md new file mode 100644 index 0000000..a8e41bf --- /dev/null +++ b/packaging/release.md @@ -0,0 +1,40 @@ +# AgenNet preview release archives + +The `v0.2.0-preview.4` release publishes one native archive for each supported +macOS and Linux architecture. Windows users run the Linux artifact inside +WSL2; native Windows is not supported by this preview. + +Each archive contains one directory with exactly the `agenet` executable, +MIT `LICENSE`, repository `README.md`, and canonical `RELEASE-METADATA.json`. +Packaging fixes member order, timestamps, ownership, modes, USTAR encoding, and +gzip metadata. The checker rejects links, traversal, additional members, +metadata drift, source-file drift, and a binary that does not report the exact +preview version. + +Build jobs must pass explicit binary, target, version, full commit, and output +paths to `scripts/package-release.sh`. Consumers must call +`scripts/check-release-archive.sh` before extraction. + +The public installer is rendered from the strict manifest. It selects one of +the four immutable release URLs, bounds the HTTPS-only download, verifies the +exact size and SHA-256 digest, rejects unsafe archive layouts, smoke-tests the +binary version, and publishes only to `~/.local/bin/agenet`. It accepts no +arguments or enrollment material, never runs `node join`, and preserves an +existing different binary. + +`scripts/verify-release.sh` verifies the manifest, regenerated installer, +checksum file, and all four archives without using the network. Its +`--complete` mode additionally requires the two raw Agent guides, the +versioned bootstrap Skill archive, and release notes. `SHA256SUMS` covers every +downloadable release asset except itself. Four GitHub artifact attestations +bind the native archives to the tag workflow. + +The tag-only publication job has the minimum `contents`, `id-token`, and +`attestations` write permissions. Branch builds cannot publish. On a rerun for +an existing tag, the job downloads every existing asset and compares the exact +name set and bytes; it never overwrites or silently replaces a divergent +prerelease. + +These archives are a Developer Preview. They do not claim native Windows, +physical two-device acceptance, arbitrary code sandboxing, or stable protocol +compatibility. diff --git a/plan/00-v2-modelhub-adapter-correction.md b/plan/00-v2-modelhub-adapter-correction.md new file mode 100644 index 0000000..d29cf78 --- /dev/null +++ b/plan/00-v2-modelhub-adapter-correction.md @@ -0,0 +1,31 @@ +# AgenNet ModelHub Adapter Correction Plan + +## Goal + +Correct the manual Walkman-backed model gate without changing AgenNet protocol semantics or hiding the first failed experiment. + +## Preconditions + +- Walkman's ignored `.env` remains the only credential source. +- Walkman and QueryAgent define the alias as a complete `gemini_multimodal_inline_v1` endpoint. +- No prompt, response body, endpoint, API key, or query-bearing URL may be logged. + +## Steps + +1. Preserve non-success HTTP status as a sanitized error category and reproduce the failure. +2. Inspect the owning Walkman/QueryAgent provider implementation rather than infer behavior from `OPENAI_BASE_URL`. +3. Add a failing contract test for exact endpoint use, `ak` query authentication, inline text messages, and response parsing. +4. Implement the separate ModelHub style while retaining the tested OpenAI style. +5. Re-run all automated gates and the real-model four-process demo. + +## Acceptance + +- The ModelHub test observes no appended `/chat/completions` route. +- Credentials remain absent from Debug output, errors, logs, repository content, and demo output. +- The real demo reports one or two genuine model calls and reaches `Accepted` without deterministic fallback. +- All prior protocol, runtime, HTTP, workload, and process tests remain green. + +## Risks + +- Query-parameter authentication can leak if raw Reqwest errors or final URLs are logged; the adapter maps errors to closed enums and never exposes the request URL. +- The endpoint contract belongs to an internal provider and may evolve; the style remains explicit and covered by a wire-level test. diff --git a/plan/01-v1-multi-host-node-bootstrap.md b/plan/01-v1-multi-host-node-bootstrap.md new file mode 100644 index 0000000..f72a57b --- /dev/null +++ b/plan/01-v1-multi-host-node-bootstrap.md @@ -0,0 +1,956 @@ +# AgenNet Multi-host Node Bootstrap Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use +> `superpowers:subagent-driven-development` (recommended) or +> `superpowers:executing-plans` to implement this plan task-by-task. Steps use +> checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Extend the verified loopback runtime into a secure, revocable +Developer Preview that can enroll and run AgenNet nodes on two physical macOS +or Linux devices connected by an existing private overlay. + +**Architecture:** Keep the signed protocol kernel independent from transport. +Introduce a Domain Root → online Enrollment Authority → Node Credential chain, +server-authenticated one-time enrollment, mutually authenticated peer TLS, +explicit private-network boundaries, signed revocation snapshots, durable local +state, and user-level service managers. Preserve loopback as a development +adapter, but move all v0.2 peers to the same authority-chain envelope model. + +**Tech Stack:** Rust 1.97.1, Axum 0.8.9, Tokio 1.53.1, Reqwest 0.13.4, +rustls 0.23.43, axum-server 0.8.0, rcgen 0.14.9, Ed25519-dalek 3.0.0, +age 0.12.1, HMAC-SHA-256, Serde, Clap, macOS LaunchAgent, Linux systemd user +service. + +## Global Constraints + +- Treat every protocol, persistence, and command default as a v0.2 Developer + Preview decision that may be revised through a versioned migration. +- Never accept `0.0.0.0`, `::`, public IP addresses, or an implicit network + interface. Bind only the exact configured loopback or private-overlay IP. +- Never transmit or persist the Domain Root passphrase, invitation secret, + invitation pepper, node signing key, or TLS private key in logs, command-line + arguments, environment variables, journal payloads, or ordinary stdin. +- Peer and enrollment HTTP clients must disable system proxies. The LLM Decision + Adapter retains its existing proxy behavior because it is not private-overlay + traffic. +- Keep all protocol effect endpoints explicitly idempotent through + `operation_id`; do not add implicit HTTP retries to invitation consumption, + enrollment, Contract creation, Event append, renewal, or revocation. +- Preserve current v0.1 tests while migrating them to the v0.2 credential chain. + No unversioned on-disk or wire-format rewrite is allowed. +- Use test-first development. A task is not complete until its focused tests, + `cargo fmt --check`, and relevant Clippy targets pass. +- Use the repository's five-section Commit Message format. Do not merge. +- Do not mark this plan complete until the two-physical-device acceptance run + passes. Local and CI simulations are necessary but not sufficient. + +--- + +## Task 1: Pin security dependencies and establish the v0.2 boundary + +**Files:** + +- Modify: `Cargo.toml` +- Modify: `Cargo.lock` +- Modify: `src/lib.rs` +- Create: `src/bootstrap/mod.rs` +- Create: `src/service/mod.rs` +- Create: `tests/version_boundary.rs` +- Modify: `README.md` +- Create: `docs/security/dependency-review-v0.2.md` + +**Interfaces:** + +```rust +pub const KERNEL_VERSION_V1: &str = "agenet.kernel.v0.1"; +pub const KERNEL_VERSION_V2: &str = "agenet.kernel.v0.2"; + +pub enum SupportedKernelVersion { + V1, + V2, +} +``` + +- [ ] Add a failing `tests/version_boundary.rs` test proving that v0.2 is the + emitted version, v0.1 can be identified for migration diagnostics, and an + unknown version returns `UnsupportedKernelVersion` rather than falling + through to Serde errors. +- [ ] Run `cargo test --test version_boundary`; confirm the missing constants + or error variant causes the expected failure. +- [ ] Pin these direct dependencies exactly: `axum-server = 0.8.0` with + `tls-rustls`, `rustls = 0.23.43`, `rcgen = 0.14.9` with `aws_lc_rs`, `pem`, + and `zeroize`, `rustls-pemfile = 2.2.0`, `age = 0.12.1`, + `rpassword = 7.5.4`, `hmac = 0.13.0` with `zeroize`, `ipnet = 2.12.1` + with `serde`, `directories = 6.0.0`, `time = 0.3.55`, and `plist = 1.10.0`. +- [ ] Bump the package to `0.2.0` without changing the Rust 1.97.1 floor. Put + `plist` behind a macOS target dependency and record each new crate's license, + maintenance status, purpose, transitive footprint, and removal boundary in + `docs/security/dependency-review-v0.2.md`. +- [ ] Add the version constants and stable typed error, export empty + `bootstrap` and `service` module boundaries, then regenerate `Cargo.lock`. +- [ ] Document the dependency purpose, v0.2 wire boundary, and the absence of + external v0.1 consumers in `README.md`. +- [ ] Run `cargo test --test version_boundary`, `cargo check --all-targets`, + and `cargo tree -d`; investigate unexpected duplicate TLS/crypto major + versions before continuing. +- [ ] Commit: + +```text +[feat][Bootstrap][1/14] Establish v0.2 boundary + +Root cause: NA +Solution: Pin the security stack and introduce an explicit v0.2 +kernel boundary with typed migration diagnostics. +Risks: The age crate remains pinned below 1.0. +Dependency: AgenNet v0.1 at fda7a6b. +Links: plan/01-v1-multi-host-node-bootstrap.md +``` + +## Task 2: Implement the Authority credential chain + +**Files:** + +- Create: `src/protocol/authority.rs` +- Modify: `src/protocol/identity.rs` +- Modify: `src/protocol/envelope.rs` +- Modify: `src/protocol/error.rs` +- Modify: `src/protocol/mod.rs` +- Modify: `src/protocol/types.rs` +- Create: `tests/authority_protocol.rs` +- Modify: `tests/protocol_kernel.rs` + +**Interfaces:** + +```rust +pub struct AuthorityClaims { + pub domain_id: DomainId, + pub authority_id: NodeId, + pub signing_public_key_base64: String, + pub tls_ca_sha256: String, + pub scopes: BTreeSet, + pub allowed_profiles: BTreeSet, + pub maximum_node_lifetime_ms: u64, + pub issued_at_ms: i64, + pub expires_at_ms: i64, +} + +pub struct SignedAuthorityCredential { + pub claims: AuthorityClaims, + pub root_signature_base64: String, +} + +pub struct CredentialChain { + pub authority: SignedAuthorityCredential, + pub node: SignedNodeCredential, +} + +pub enum BootstrapProfile { + Base, + Provider, + AgentCandidate, +} + +pub fn verify_credential_chain( + root_public_key: &VerifyingKey, + chain: &CredentialChain, + expected_domain: &DomainId, + expected_role: NodeRole, + now_ms: i64, +) -> Result; +``` + +- [ ] Write failing tests for valid chain verification, wrong root, Domain + mismatch, expired Authority, expired Node, missing issuance scope, Node issuer + mismatch, role mismatch, TLS CA fingerprint tampering, and every signature bit + mutation. +- [ ] Add a property test that mutates one serialized Authority-claim byte and + proves `verify_strict` rejects it. +- [ ] Change `SignedNodeCredential` claims to include `domain_id`, + `authority_id`, `bootstrap_profile`, `allowed_roles`, and the bounded + Authority-signed `capability_ceiling`. Node Credential v0.3 rejects older + semantics. `Base` permits the Requester role, `Provider` adds Executor and + Verifier, and `AgentCandidate` remains Base-equivalent until a separately + authorized Adapter is enabled. Directory issuance is reserved for `domain + init`, uses an explicit empty ceiling, and is not an invitation. +- [ ] Make issuance require an Authority signing key, `IssueNodeCredential` + scope, an allowed Bootstrap Profile, and a lifetime within the Authority + ceiling. +- [ ] Replace `WireEnvelope.credential` with + `WireEnvelope.credential_chain`; make v0.2 verification validate the chain + before deserializing the exact signed payload bytes. +- [ ] Keep a read-only v0.1 parser solely to return a stable + `MigrationRequiredV1Credential` error. Do not accept v0.1 objects on v0.2 + effect endpoints. +- [ ] Run `cargo test --test authority_protocol --test protocol_kernel` and + `cargo clippy --test authority_protocol -- -D warnings`. +- [ ] Commit: + +```text +[feat][Bootstrap][2/14] Add authority credentials + +Root cause: NA +Solution: Replace direct Root-to-node trust with a versioned +Root-to-Authority-to-node chain and strict validation. +Risks: Retained v0.1 demo state requires a new run rather than +in-place credential reuse. +Dependency: Bootstrap step 1. +Links: docs/superpowers/specs/ +2026-08-14-node-bootstrap-and-pages-design.md +``` + +## Task 3: Enforce explicit private-network boundaries + +**Files:** + +- Create: `src/bootstrap/network.rs` +- Modify: `src/protocol/error.rs` +- Modify: `src/transport/client.rs` +- Modify: `src/node.rs` +- Create: `tests/network_boundary.rs` + +**Interfaces:** + +```rust +pub enum OverlayKind { + Loopback, + Tailscale, + WireGuard, +} + +pub struct NetworkBoundary { + pub kind: OverlayKind, + pub bind_ip: IpAddr, + pub allowed_cidrs: Vec, +} + +impl NetworkBoundary { + pub fn validate_bind(&self) -> Result<(), RuntimeError>; + pub fn allows_peer(&self, peer: IpAddr) -> bool; +} +``` + +- [ ] Write failing table tests for IPv4/IPv6 loopback, the Tailscale CGNAT + range `100.64.0.0/10`, explicit Tailscale IPv6 addresses, an explicit + WireGuard CIDR, public addresses, wildcard addresses, multicast, unspecified, + link-local, and a bind address outside the declared CIDR. +- [ ] Require an explicit `bind_ip`. For Tailscale, accept only an address + reported by the local Tailscale interface and contained in its assigned + address set; do not treat the entire CGNAT range as proof that the local + interface owns the address. For WireGuard, require at least one operator- + supplied CIDR. +- [ ] Add peer URL validation that rejects scheme downgrade, DNS hostnames, + userinfo, fragments, and IPs outside `allowed_cidrs` before opening a socket. +- [ ] Retain `127.0.0.1`/`::1` for tests; require `https` for all non-loopback + peer and enrollment endpoints. +- [ ] Run `cargo test --test network_boundary --test http_client` and verify + no existing loopback regression. +- [ ] Commit: + +```text +[feat][Bootstrap][3/14] Enforce network boundary + +Root cause: NA +Solution: Validate exact private-overlay listeners and peers before +creating sockets or HTTP requests. +Risks: Overlay interface discovery can differ across OS releases. +Dependency: Bootstrap step 2. +Links: plan/01-v1-multi-host-node-bootstrap.md +``` + +## Task 4: Add the encrypted administrative keystore and internal TLS PKI + +**Files:** + +- Create: `src/bootstrap/keystore.rs` +- Create: `src/bootstrap/pki.rs` +- Modify: `src/runtime/key_store.rs` +- Create: `tests/admin_keystore.rs` +- Create: `tests/pki.rs` + +**Interfaces:** + +```rust +pub struct DomainRootMaterial { + pub domain_id: DomainId, + pub signing_key: SigningKey, + pub created_at_ms: i64, +} + +pub trait RootKeystore { + fn create( + path: &Path, + material: &DomainRootMaterial, + passphrase: SecretString, + ) -> Result<(), BootstrapError>; + fn unlock( + path: &Path, + passphrase: SecretString, + ) -> Result, BootstrapError>; +} + +pub struct AuthorityPki { + pub ca_cert_pem: Zeroizing, + pub ca_key_pem: Zeroizing, + pub fingerprint_sha256: String, +} +``` + +- [ ] Write failing keystore tests for a correct passphrase, wrong passphrase, + truncated ciphertext, non-regular files, symlinks, `0600` permissions, atomic + creation, and zeroization at the API boundary. +- [ ] Implement a versioned keystore header and age scrypt passphrase + encryption. Obtain passphrases with `rpassword` from a controlling TTY; reject + args, env vars, pipes, and ordinary stdin. +- [ ] Write failing PKI tests for CA constraints, server SAN exact-IP matching, + client certificate identity binding, validity not exceeding Authority expiry, + CSR key ownership, unknown critical extensions, and fingerprint stability. +- [ ] Use `rcgen` to create one internal Authority CA, a server certificate for + the exact overlay IP, and CSR-signed node client certificates. Persist all + private material with owner-only permissions and atomic rename. +- [ ] Keep the Domain Root outside daemon state. The daemon receives only the + Root public key, signed Authority Credential, Authority signing key, TLS CA, + and Authority server key. +- [ ] Run `cargo test --test admin_keystore --test pki` and use a sentinel + passphrase to assert logs and `Debug` errors never contain it. +- [ ] Commit: + +```text +[feat][Bootstrap][4/14] Add encrypted PKI + +Root cause: NA +Solution: Store the Domain Root in a passphrase-encrypted administrative +keystore and generate a scoped internal TLS Authority. +Risks: Losing the Root passphrase prevents administrative recovery. +Dependency: Bootstrap step 3. +Links: docs/superpowers/specs/ +2026-08-14-node-bootstrap-and-pages-design.md +``` + +## Task 5: Implement durable one-time invitations + +**Files:** + +- Create: `src/bootstrap/invitation.rs` +- Create: `src/bootstrap/journal.rs` +- Modify: `src/protocol/error.rs` +- Create: `tests/invitation_store.rs` + +**Interfaces:** + +```rust +pub struct InvitationRecord { + pub invitation_id: Uuid, + pub secret_hmac_sha256: [u8; 32], + pub allowed_profile: BootstrapProfile, + pub capability_ceiling: BTreeSet, + pub expires_at_ms: i64, + pub failed_attempts: u8, + pub state: InvitationState, +} + +pub struct InvitationPublicClaims { + pub protocol_version: String, + pub domain_id: DomainId, + pub authority_endpoint: Url, + pub directory_seeds: Vec, + pub root_sha256: String, + pub tls_ca_sha256: String, + pub allowed_profile: BootstrapProfile, + pub capability_ceiling: BTreeSet, + pub invitation_id: Uuid, + pub expires_at_ms: i64, + pub maximum_attempts: u8, +} + +pub enum InvitationState { + Available, + Reserved { operation_id: Uuid, reserved_at_ms: i64 }, + Consumed { node_id: NodeId, consumed_at_ms: i64 }, + Locked, + Expired, +} +``` + +- [ ] Write failing tests proving invitation secrets contain 256 random bits, + only the HMAC is persisted, the pepper is stored separately with `0600`, the + default expiry is ten minutes, the fifth failure locks the invitation, + concurrent valid claims yield exactly one winner, and repeating the winning + `operation_id` returns the same result. +- [ ] Define an append-only invitation journal with serialized writes, + `flush`, `sync_data`, replay, a length-delimited record, and a checksum. +- [ ] Implement a two-phase `reserve` then `consume` transition. A failed CSR or + certificate issuance releases only the same operation's reservation; a + successful issuance consumes permanently. +- [ ] Encode the operator handoff as public claims plus a 256-bit + `SecretString`. Its `Debug` output is always redacted; only explicit hidden- + TTY display/input functions may materialize the complete value. +- [ ] Ensure lookup and HMAC comparison are constant-time and errors do not + distinguish unknown IDs from wrong secrets. +- [ ] Add property tests for crash/replay at every transition and invariants + `consumed <= 1`, `failed_attempts <= 5`, and `locked => !available`. +- [ ] Run `cargo test --test invitation_store` including a multi-threaded race + loop of at least 1,000 attempts under the test process. +- [ ] Commit: + +```text +[feat][Bootstrap][5/14] Add one-time invitations + +Root cause: NA +Solution: Add durable HMAC invitations with expiration, lockout, +reservation, consumption, replay, and idempotency. +Risks: Durability still inherits the host volume's guarantees. +Dependency: Bootstrap step 4. +Links: plan/01-v1-multi-host-node-bootstrap.md +``` + +## Task 6: Implement pinned enrollment and local-key CSR issuance + +**Files:** + +- Create: `src/protocol/enrollment.rs` +- Create: `src/bootstrap/enrollment.rs` +- Create: `src/transport/enrollment.rs` +- Modify: `src/transport/mod.rs` +- Modify: `src/protocol/mod.rs` +- Create: `tests/enrollment_protocol.rs` +- Create: `tests/http_enrollment.rs` + +**Interfaces:** + +```rust +pub struct EnrollmentRequest { + pub operation_id: Uuid, + pub invitation_id: Uuid, + pub invitation_secret: SecretString, + pub node_id: NodeId, + pub requested_profile: BootstrapProfile, + pub signing_public_key_base64: String, + pub tls_csr_pem: String, +} + +pub struct EnrollmentBundle { + pub domain_id: DomainId, + pub root_public_key_base64: String, + pub credential_chain: CredentialChain, + pub tls_client_certificate_pem: String, + pub tls_ca_certificate_pem: String, + pub authority_endpoint: Url, + pub revocation_endpoint: Url, +} +``` + +- [ ] Write pure protocol tests for payload size, role/profile scope, CSR + identity, operation ID, invitation expiration, and sanitized stable errors. +- [ ] Write HTTP tests with a generated Authority server certificate proving + success only when the caller pins the expected CA fingerprint and exact IP + SAN. Reject redirects, DNS fallback, system proxy interception, mismatched + fingerprint, mismatched CSR key, reused secret, and oversized bodies. +- [ ] Build a separate enrollment listener using server-auth TLS. It must not + share the mTLS peer port because an unenrolled node has no client certificate. +- [ ] Generate Ed25519 protocol and TLS keys locally on the joining node, + submit only public material and the CSR, then validate every returned + credential and certificate before committing local state. +- [ ] Configure the enrollment Reqwest client with `.no_proxy()`, redirect + policy `none`, fixed connect/request timeouts, the invitation-pinned TLS CA + certificate, and no ambient credential store. +- [ ] Zeroize the invitation secret immediately after the enrollment response + is validated or rejected. +- [ ] Run `cargo test --test enrollment_protocol --test http_enrollment` and + scan captured logs for invitation and private-key sentinels. +- [ ] Commit: + +```text +[feat][Bootstrap][6/14] Add pinned enrollment + +Root cause: NA +Solution: Add fingerprint-pinned enrollment that signs a local CSR and +consumes a one-time invitation. +Risks: Enrollment requires clock skew within the documented tolerance. +Dependency: Bootstrap step 5. +Links: docs/superpowers/specs/ +2026-08-14-node-bootstrap-and-pages-design.md +``` + +## Task 7: Add signed revocation snapshots and stale-policy behavior + +**Files:** + +- Create: `src/protocol/revocation.rs` +- Create: `src/runtime/revocation.rs` +- Modify: `src/protocol/authority.rs` +- Modify: `src/transport/directory.rs` +- Modify: `src/transport/node.rs` +- Create: `tests/revocation.rs` +- Create: `tests/http_revocation.rs` + +**Interfaces:** + +```rust +pub struct RevocationSnapshot { + pub domain_id: DomainId, + pub epoch: u64, + pub generated_at_ms: i64, + pub next_update_ms: i64, + pub revoked_authorities: BTreeSet, + pub revoked_nodes: BTreeSet, + pub signature_base64: String, +} + +pub enum RevocationDecision { + CurrentAndAllowed, + Revoked, + Stale, +} +``` + +- [ ] Write failing tests for signature tampering, Domain mismatch, epoch + rollback, stale snapshots, revoked Authority, revoked Node, replay of the same + epoch, atomic cache replacement, and restart recovery. +- [ ] Add an Authority-signed snapshot endpoint with a monotonic persisted + epoch. Default `next_update_ms` to no more than five minutes after generation. +- [ ] Add each node's atomic snapshot cache and refresh task. Read-only health + and snapshot refresh remain available while stale; Contract creation, Event + append, Artifact read, registration, and all other effectful protocol requests + fail closed with `RevocationStateStale`. +- [ ] Reject revoked peers both during TLS identity mapping and during envelope + credential validation; retain the stable peer/operation identifiers needed + for audit without logging full credentials. +- [ ] Run `cargo test --test revocation --test http_revocation` with a paused + Tokio clock for deterministic freshness tests. +- [ ] Commit: + +```text +[feat][Bootstrap][7/14] Enforce signed revocation + +Root cause: NA +Solution: Distribute monotonic signed revocation snapshots and fail +closed on stale policy for effectful peer operations. +Risks: Authority downtime pauses work after snapshot freshness ends. +Dependency: Bootstrap step 6. +Links: plan/01-v1-multi-host-node-bootstrap.md +``` + +## Task 8: Replace non-loopback HTTP with mutual TLS + +**Files:** + +- Create: `src/transport/tls.rs` +- Modify: `src/transport/client.rs` +- Modify: `src/transport/directory.rs` +- Modify: `src/transport/node.rs` +- Create: `src/runtime/node.rs` +- Create: `tests/http_mtls.rs` + +**Interfaces:** + +```rust +pub struct PeerTlsIdentity { + pub node_id: NodeId, + pub certificate_chain_pem: Zeroizing, + pub private_key_pem: Zeroizing, + pub authority_ca_pem: String, +} + +pub fn build_peer_server_config( + identity: &PeerTlsIdentity, + revocations: Arc, +) -> Result; + +pub fn build_peer_client( + identity: &PeerTlsIdentity, + boundary: &NetworkBoundary, +) -> Result; +``` + +- [ ] Write tests proving mTLS success, missing client certificate rejection, + wrong CA rejection, exact-IP SAN enforcement, expired certificate rejection, + revoked certificate rejection, envelope/certificate Node-ID mismatch + rejection, redirect rejection, system proxy bypass, and private-boundary + validation before connect. +- [ ] Build the rustls server verifier with the Authority CA and require client + authentication. Map the verified certificate identity into request + extensions; every signed-envelope handler must compare it with `issuer_id`. +- [ ] Serve with `axum_server::bind_rustls` using a `RustlsConfig` created from + the explicit `rustls::ServerConfig`; retain a handle for graceful shutdown. +- [ ] Build the Reqwest peer client with explicit CA, PKCS#8 identity, + `.no_proxy()`, redirect policy `none`, connect timeout two seconds, request + timeout five seconds, and current body limits. +- [ ] Keep plain HTTP only for loopback tests. Return + `UnsupportedInsecureTransport` for any non-loopback `http` endpoint. +- [ ] Run `cargo test --test http_mtls --test http_directory --test http_client` + and `cargo clippy --all-targets --all-features -- -D warnings`. +- [ ] Commit: + +```text +[feat][Bootstrap][8/14] Add mutual TLS transport + +Root cause: NA +Solution: Require Authority-issued mTLS and bind the TLS identity to +every signed envelope issuer. +Risks: TLS rotation must complete before certificate expiration. +Dependency: Bootstrap step 7. +Links: docs/superpowers/specs/ +2026-08-14-node-bootstrap-and-pages-design.md +``` + +## Task 9: Define versioned local configuration and durable node state + +**Files:** + +- Create: `src/bootstrap/config.rs` +- Create: `src/bootstrap/paths.rs` +- Create: `src/bootstrap/state.rs` +- Modify: `src/runtime/key_store.rs` +- Create: `tests/bootstrap_config.rs` +- Create: `tests/bootstrap_recovery.rs` + +**Interfaces:** + +```rust +pub struct NodeConfigV1 { + pub schema_version: u32, + pub domain_id: DomainId, + pub profile: BootstrapProfile, + pub network: NetworkBoundary, + pub directory_seeds: Vec, + pub authority_endpoint: Url, + pub revocation_endpoint: Url, +} + +pub enum BootstrapPhase { + Absent, + BinaryInstalled, + ReadyForEnrollment, + CredentialIssued, + ServicePrepared, + Registered, + Healthy, + Left, +} +``` + +Review correction: bootstrap journal schema 2 uses the order shown above and +rejects schema 1. Task 10 stops at `CredentialIssued`; Task 11 owns the first +real `ServicePrepared` artifact and transition. Node Credential v0.3 carries +the exact Authority-signed invitation `capability_ceiling`; Directory +registration must enforce it, and Tasks 12/14 must preserve the verified +claims when wiring adapters and node setup automation. + +- [ ] Write tests for macOS and Linux user paths, schema rejection, unknown + fields, symlinks, owner mismatch, wrong permissions, partial writes, interrupted + enrollment, interrupted service installation, and replay to the last committed + phase. +- [ ] Use `directories` only to resolve user-scoped roots. Store configuration, + credentials, service metadata, revocation cache, and journals in separate + named files with explicit version fields. +- [ ] Implement an atomic bootstrap state journal so `join`, service + preparation, `leave`, and `uninstall` can resume or roll back without guessing + from partial filesystem state. +- [ ] Make config parsing deny unknown fields and validate all paths, URLs, + network boundaries, credential chains, and permissions before runtime startup. +- [ ] Run `cargo test --test bootstrap_config --test bootstrap_recovery`. +- [ ] Commit: + +```text +[feat][Bootstrap][9/14] Persist bootstrap state + +Root cause: NA +Solution: Add versioned user configuration and a crash-recoverable +bootstrap phase journal. +Risks: Cross-device atomic rename is rejected. +Dependency: Bootstrap step 8. +Links: plan/01-v1-multi-host-node-bootstrap.md +``` + +## Task 10: Expose Domain, invitation, and join CLI commands + +**Files:** + +- Modify: `src/main.rs` +- Create: `src/cli/mod.rs` +- Create: `src/cli/domain.rs` +- Create: `src/cli/invite.rs` +- Create: `src/cli/join.rs` +- Create: `src/cli/output.rs` +- Create: `tests/cli_bootstrap.rs` + +**Command contract:** + +```text +agenet domain init --network --bind-ip + [--allowed-cidr ] +agenet invite create --profile [--ttl 10m] +agenet node join [--bind-ip ] +``` + +- [x] Write CLI tests for help, exact required arguments, invalid network + combinations, non-TTY rejection, pre-existing Domain state, JSON output, and + secret redaction. Capture process lists to prove passphrases and invitation + secrets never appear in argv. +- [x] Make `domain init` request and confirm a passphrase on the controlling + TTY, create the Root and Authority, write the encrypted Root keystore, and + output only Domain ID, endpoints, Root fingerprint, and next commands. +- [x] Make `invite create` unlock the Root keystore, authorize the online + Authority scope, and print the invitation secret exactly once to the + controlling TTY. Structured JSON output contains only invitation metadata and + must never contain the secret. +- [x] Make `node join` read the complete invitation through hidden TTY input, + validate its public Domain, Authority, Directory, profile, capability ceiling, + expiry, attempt count, and fingerprint fields, generate keys locally, perform + pinned enrollment, persist validated state atomically, and return a stable + `JoinResult`. +- [x] Add `--output human|json` for automation. Keep stable `code`, `message`, + `retryable`, and `operation_id` fields in JSON errors. +- [x] Bind Domain-owned overlay policy into invitation v3 and the requested + bind IP into signed enrollment v0.3; issue an exact-IP dual-use peer + certificate and reject v2 without guessing missing authorization policy. +- [x] Run `cargo test --test cli_bootstrap` and manually inspect `agenet + --help`, `agenet domain --help`, `agenet invite --help`, and `agenet node + join --help`. +- [ ] Commit: + +```text +[feat][Bootstrap][10/14] Add bootstrap commands + +Root cause: NA +Solution: Expose Domain, invitation, and pinned enrollment through one +typed CLI with TTY-only secret entry. +Risks: Headless enrollment requires an interactive operator. +Dependency: Bootstrap step 9. +Links: docs/superpowers/specs/ +2026-08-14-node-bootstrap-and-pages-design.md +``` + +## Task 11: Install and manage user-level services + +**Files:** + +- Modify: `src/service/mod.rs` +- Create: `src/service/macos.rs` +- Create: `src/service/linux.rs` +- Create: `src/cli/node.rs` +- Modify: `src/cli/mod.rs` +- Create: `tests/service_macos.rs` +- Create: `tests/service_linux.rs` + +**Interfaces:** + +```rust +pub trait UserServiceManager { + fn render(&self, spec: &ServiceSpec) -> Result, ServiceError>; + fn install(&self, spec: &ServiceSpec) -> Result; + fn start(&self) -> Result; + fn stop(&self) -> Result; + fn uninstall(&self) -> Result<(), ServiceError>; + fn status(&self) -> Result; +} +``` + +- [ ] Write pure rendering tests for a macOS LaunchAgent plist and Linux + `systemd --user` unit. Assert absolute executable/config paths, argument + escaping, restart limits, no secrets, no shell, no root paths, and no + environment-file injection. +- [ ] Write command-runner contract tests for idempotent install/start/stop, + rollback after activation failure, status mapping, unavailable systemd user + session, and a binary path containing spaces. +- [ ] Implement + `~/Library/LaunchAgents/org.nexa-language.agenet.plist` with + `RunAtLoad`, bounded `KeepAlive`, stdout/stderr files under AgenNet's state + root, and `launchctl bootstrap/bootout` in the user's GUI domain. +- [ ] Implement `~/.config/systemd/user/agenet.service` with + `Restart=on-failure`, bounded restart delay, hardening options valid for a + user unit, `daemon-reload`, and `enable --now`. +- [ ] Explicitly report that persistence begins after user login. Do not invoke + sudo, create system units, or enable Linux lingering. +- [ ] Integrate service preparation into `domain init` and `node join` only + after a separate explicit confirmation. Add `agenet node start|stop|status` + and make every command reconcile durable bootstrap phase state. Service + removal remains owned by the lifecycle commands in Task 13. +- [ ] Run `cargo test --test service_macos --test service_linux`; run the native + platform smoke test in a temporary user-scoped service label and clean it up + through the tested uninstall path. +- [ ] Commit: + +```text +[feat][Bootstrap][11/14] Manage user node service + +Root cause: NA +Solution: Add recoverable LaunchAgent and systemd user services without +root privileges or embedded secrets. +Risks: Nodes start only after the owning user logs in. +Dependency: Bootstrap step 10. +Links: plan/01-v1-multi-host-node-bootstrap.md +``` + +## Task 12: Integrate the v0.2 runtime and migrate the loopback demo + +**Files:** + +- Modify: `src/node.rs` +- Modify: `src/demo.rs` +- Modify: `src/runtime/node.rs` +- Modify: `src/runtime/directory.rs` +- Modify: `src/runtime/requester.rs` +- Modify: `src/runtime/provider.rs` +- Modify: `tests/multiprocess_demo.rs` +- Create: `tests/multihost_simulated.rs` + +- [ ] Update the four-process test first so every node has a v0.2 Authority + chain and certificate, Requester still knows only Directory seeds, and all + endpoints derive from signed manifests. +- [ ] Add a simulated multi-host test binding distinct loopback aliases and + enforcing HTTPS/mTLS, revocation freshness, two bilateral Contracts, + independent metrics verification, and final `Accepted`. +- [ ] Refactor `NodeRuntime` to load validated `NodeConfigV1`, credential chain, + TLS identity, network boundary, and revocation cache before binding. Remove + the static validation timestamp from `NodeIdentity`; use an injected clock at + request validation time. +- [ ] Register manifests only after peer TLS is ready and the revocation + snapshot is current. Reject manifest endpoints outside the provider's + declared boundary or with a TLS identity different from the signed provider. +- [ ] Preserve `agenet demo` as an explicit loopback harness. It may provision + an ephemeral Authority automatically but must not read the administrative + keystore or claim multi-host security. +- [ ] Run `cargo test --test multiprocess_demo --test multihost_simulated` and + verify all spawned processes and listener ports are reaped. +- [ ] Commit: + +```text +[feat][Bootstrap][12/14] Integrate host runtime + +Root cause: NA +Solution: Run all four roles through the v0.2 +credential, TLS, network-boundary, and revocation paths. +Risks: The simulated test cannot replace physical overlay acceptance. +Dependency: Bootstrap step 11. +Links: docs/design/agenet-v0.1.md +``` + +## Task 13: Complete renewal, revoke, leave, uninstall, and doctor + +**Files:** + +- Create: `src/cli/lifecycle.rs` +- Create: `src/cli/doctor.rs` +- Modify: `src/cli/mod.rs` +- Modify: `src/cli/node.rs` +- Modify: `src/bootstrap/enrollment.rs` +- Modify: `src/runtime/revocation.rs` +- Create: `tests/lifecycle_cli.rs` +- Create: `tests/doctor_cli.rs` +- Modify: `README.md` +- Modify: `docs/design/agenet-v0.1.md` + +**Command contract:** + +```text +agenet credential renew +agenet node revoke +agenet node leave +agenet uninstall [--purge] +agenet node doctor [--output json] +``` + +- [ ] Write failure-first tests for renewal before expiry, expired credentials, + revoked nodes, Authority unavailability, repeated leave/uninstall, state + preservation, noninteractive destructive confirmation rejection, and + sanitized doctor output. +- [ ] Implement renewal as a new local CSR authenticated by current mTLS and + signed envelope; never reuse a TLS private key. Validate and atomically swap + the new bundle, then reconnect before deleting the retired key. +- [ ] Implement revoke as an administrative Root-authorized Authority action + that increments the snapshot epoch. Require typed confirmation containing the + target Node ID. +- [ ] Implement leave as unregister plus service stop and a best-effort signed + departure Event; preserve identity and audit state. Implement uninstall as + service and binary removal while retaining state by default. `--purge` + requires interactive confirmation and lists every identity/journal path; the + Root keystore is never deleted by node uninstall. +- [ ] Implement doctor checks for file permissions, config schema, clock skew, + exact bind ownership, TLS validity, Authority reachability, revocation age, + Directory reachability, service state, and binary/config version compatibility. +- [ ] Update the README and design verification matrix with exact implemented + and deferred claims. +- [ ] Run `cargo test --test lifecycle_cli --test doctor_cli`, then + `cargo fmt --check`, `cargo clippy --all-targets --all-features -- -D + warnings`, and `cargo test --all-targets`. +- [ ] Commit: + +```text +[feat][Bootstrap][13/14] Complete node lifecycle + +Root cause: NA +Solution: Add renewal, revocation, recoverable leave and uninstall, and +machine-readable diagnostics. +Risks: Uninstall cannot remove overlay software outside AgenNet. +Dependency: Bootstrap step 12. +Links: plan/01-v1-multi-host-node-bootstrap.md +``` + +## Task 14: Pass the physical two-device acceptance gate + +**Files:** + +- Create: `docs/testing/two-device-acceptance.md` +- Create: `scripts/verify-two-device-evidence.sh` +- Create: `tests/fixtures/two-device-evidence.schema.json` +- Modify: `ROADMAP.md` +- Modify: `README.md` +- Modify: `docs/design/agenet-v0.1.md` + +- [ ] Write the evidence JSON Schema first. Require redacted device labels, + OS/architecture, AgenNet version, Domain/Node IDs, private endpoint classes, + certificate fingerprints, revocation epochs, Contract/Event IDs, Artifact + hash and metrics, timestamps, and test result; forbid invitation material, + IP addresses, usernames, paths, prompts, keys, and credentials. +- [ ] Write `scripts/verify-two-device-evidence.sh` to validate the schema, + distinct nodes, distinct device labels, v0.2 credentials, full Contract path, + matching independent metrics, final `Accepted`, post-revocation rejection, + and bounded clock skew. +- [ ] On device A, create the Domain and invitation through TTY, start Directory + and Requester services, and record sanitized doctor output. +- [ ] On device B, join through the pinned Authority endpoint, start Executor + and Verifier profiles, and prove Requester learns their endpoints only from + signed manifests. +- [ ] Execute the real `source.metrics.v1` pursuit. Record both bilateral + Contracts, independent Evidence, final `Accepted`, process identities, and + stage timings in the schema without secrets or full network addresses. +- [ ] Revoke device B from device A, refresh snapshots, then prove a new + effectful request from B is rejected while health and audit inspection remain + available. +- [ ] Use development machines to run clock-skew, restart-during-enrollment, + invitation race, stale revocation, Directory restart, and capability-churn + scenarios. Record failures and design changes in `ROADMAP.md`. +- [ ] Run the evidence verifier, all Rust quality gates, and a repository secret + scan. Update README/design claims only after evidence passes. +- [ ] Commit: + +```text +[milestone][Bootstrap][14/14] Verify host preview + +Root cause: NA +Solution: Validate enrollment, discovery, execution, recovery, and +revocation across two physical private-overlay devices. +Risks: Results establish a Developer Preview, not Internet-scale or +public-network security. +Dependency: Bootstrap step 13 and two operator-controlled devices. +Links: docs/testing/two-device-acceptance.md +``` + +## Final Acceptance Commands + +```bash +cargo fmt --check +cargo clippy --all-targets --all-features -- -D warnings +cargo test --all-targets +cargo run -- demo \ + --env-file /Users/bytedance/proj/bandai/Walkman/.env \ + --artifact fixtures/sample.rs +scripts/verify-two-device-evidence.sh \ + .local/evidence/two-device-acceptance.json +``` + +The milestone remains incomplete if only the simulated test passes, if either +physical device uses a public or wildcard listener, if revocation is not +observed by the remote node, or if any secret appears in evidence, logs, argv, +environment, repository history, or public documentation. diff --git a/plan/01-v2-multi-host-node-bootstrap.md b/plan/01-v2-multi-host-node-bootstrap.md new file mode 100644 index 0000000..a4fafed --- /dev/null +++ b/plan/01-v2-multi-host-node-bootstrap.md @@ -0,0 +1,142 @@ +# AgenNet Task 11 bootstrap-supervisor revision + +This focused revision amends Task 11 of +[`01-v1-multi-host-node-bootstrap.md`](01-v1-multi-host-node-bootstrap.md). +All other tasks and global constraints remain unchanged. + +## Goal + +Install and manage a real login-scoped user service without claiming the +Task 12 network runtime already exists. The service runs an internal +`agenet node service-run --config ` bootstrap supervisor. It +validates the persisted startup bundle and schema-2 bootstrap journal, holds +the journal's single-instance lock, handles termination gracefully, and makes +no listener, registration, readiness, or health claim. + +## Preconditions + +- Task 10 is complete at commit `ba2f394` and leaves both founding and joined + nodes at `CredentialIssued`. +- Task 9 startup-bundle validation and owner-only atomic publication remain the + persistence boundary. +- Task 12 will attach the v0.2 runtime to the same internal service entrypoint. + +## Steps + +1. Add failure-first rendering, runner, supervisor, and CLI tests. +2. Render owner-only LaunchAgent and systemd user definitions with absolute + argv paths, no shell, environment file, secret, sudo, system unit, or linger. +3. Resolve every absolute service-path ancestor from a root directory + descriptor, rejecting symlinks, foreign owners, and group/world-writable + directories. Publish the definition atomically, append `ServicePrepared`, + then activate and verify the supervisor. +4. On activation failure, invoke the platform uninstall once. Append + `RollbackService` only after the manager reports the process stopped and + the exact service artifact is observably absent. Any stop, removal, status, + or durability uncertainty retains `ServicePrepared` and returns the stable + `ServiceRollbackIncomplete` error while preserving credentials. +5. Make start idempotently reconcile definition, process, and phase; make stop + retain the definition and `ServicePrepared`; report process state separately + from `runtime_ready: false`. +6. Verify both renderers and injected runners, run a uniquely labelled native + smoke test where the host session permits it, and record any real platform + restriction without weakening the automated contract. + +## Acceptance criteria + +- `node start`, `node stop`, and `node status` are Clap-valid and emit typed, + sanitized output. +- `ServicePrepared` is appended only after a durable service artifact exists. +- Activation failure restores `CredentialIssued` only after stopped process + and durable artifact removal are both verified. Uncertain compensation + retains `ServicePrepared`, reports `ServiceRollbackIncomplete`, and preserves + the credential for explicit reconciliation on a later start/status attempt. +- A running supervisor has validated startup material, owns the bootstrap lock, + shuts down on a termination signal, opens no network listener, and never + reports `Registered` or `Healthy`. +- macOS and Linux definitions implement their Task 11 login-scoped defaults and + pass security/static scans plus the full Rust quality gates. + +## Risks + +- Login-scoped persistence is unavailable before the owner logs in; Linux + linger and system services remain out of scope. +- Task 11 proves service supervision, not runtime reachability. Until Task 12, + `runtime_ready` is always false. +- Platform service-manager behavior depends on an available GUI/systemd user + session; absence is a typed operational error, not a simulated success. +- A post-delete directory-sync or status failure can leave the artifact absent + while the journal remains `ServicePrepared`; retry republishes and reconciles + instead of guessing whether cleanup was durable. + +## Task 12 protocol and runtime amendment + +Task 12 found that URL-only Directory seeds and single-role runtime views were +load-bearing ambiguities. The version mapping is now explicit: + +- Invitation public claims, handoff, and invitation journal use v4. +- Enrollment request, wire protocol, and durable result use v0.4. +- Node config uses schema 2 with `DirectorySeed { endpoint, node_id }`. +- Node Credential remains v0.3 and bootstrap journal remains schema 2. + +Legacy URL-only forms fail closed. Seed ordering is signed; duplicate endpoint +or NodeId entries are rejected, and TLS plus signed responses must both match +the exact seed NodeId. Authorization comes from the full verified signed role +set. Bootstrap profile is enrollment policy only. A Provider may publish +Executor and Verifier manifests from one mTLS endpoint, but Contract capability +dispatch occurs only after the signed role, Grant, Contract capability, and +credential ceiling all agree. + +The host supervisor loads config, credential chain, TLS identity, network +boundary, and current revocation state before exact bind. It publishes runtime +readiness only after mTLS health and required registration. A signed Requester +without explicit secure local-control/model configuration remains a healthy +base runtime with pursuits disabled. All request-time credential and revocation +checks use an injected live clock. The demo is an ephemeral loopback harness; +its alias/port mTLS evidence cannot replace Task 14 physical-device acceptance. + +Task 12 review clarified that request-time includes work detached after HTTP +admission. HostRuntime passes one live clock and one mutable revocation-cache +view into identity, Recorder, Artifact access, Provider, Requester, and outbound +peer policy. Recorder reopens the exact Event envelope and revalidates the +stored signed Contract under its mutation lock immediately before append. +Artifact access revalidates at authorization and immediately before the +content-addressed read. If credential, Contract, Grant, Authority, node, or +snapshot freshness becomes invalid, delayed work stops without adding a +`Failed` Event or advancing read/network counters. This is a local +serialization guarantee, not globally atomic revocation propagation. + +Task 12 role-binding review adds a single protocol registry for exact +Capability ID, versioned kind, and Provider role. All Contract, Directory, +Provider, Recorder, Artifact, and Requester decisions reuse it; unknown IDs and +ID/kind cross-pairs fail closed before effects. Grant scope and signed +capability ceiling remain separate mandatory checks. Enrollment's `Provider` +profile may explicitly enable multiple permitted roles, but the loopback demo +uses four least-privilege credentials so its verification path cannot be +accidentally satisfied by a Provider holding both Executor and Verifier roles. + +## Task 13 lifecycle amendment + +Task 13 adds `credential renew`, Root-authorized `node revoke`, recoverable +`node leave`, managed `uninstall [--purge]`, and deterministic read-only +`node doctor`. Renewal does not rotate the Node Ed25519 identity. It creates a +fresh TLS key and CSR over the existing authenticated mTLS session, while the +Authority preserves the exact NodeId, role set, capability ceiling, profile, +and Domain. + +The original multi-file startup layout was insufficient for renewal because a +crash could expose a valid mixture of old and new files. The focused v3 plan +replaces renewal publication with complete UUID identity generations and one +atomic active pointer. Legacy material is read only while no pointer exists. +After a pointer exists, startup never combines or falls back to legacy files. +The real user service must restart, publish readiness from the selected new +generation, and pass new-identity mTLS health before retired identity cleanup. + +Revocation requires a controlling TTY, exact target confirmation, and a +short-lived Root-signed authorization bound to target, current epoch, operation +ID, and Domain. Leave retains identity/config/journal state and records either +a signed Directory receipt or a durable pending departure. Default uninstall +removes only a service artifact and an exact trusted managed-user binary; +`--purge` requires NodeId plus `PURGE` confirmation and never removes Root or +founding administrative/Authority material. Doctor performs bounded read-only +checks with deterministic, sanitized codes and stable exit status. diff --git a/plan/01-v3-multi-host-node-bootstrap.md b/plan/01-v3-multi-host-node-bootstrap.md new file mode 100644 index 0000000..a01d6a5 --- /dev/null +++ b/plan/01-v3-multi-host-node-bootstrap.md @@ -0,0 +1,92 @@ +# AgenNet multi-host bootstrap plan v3 — lifecycle identity generations + +## Goal + +Close the Task 13 persistence gap discovered while implementing credential +renewal. A renewal must never expose a startup bundle assembled from old and +new credential/TLS files, and lifecycle cleanup must remain recoverable. + +## Preconditions + +- Tasks 1–12 are complete at commit `595850e`. +- Node Ed25519 identity is retained during renewal; only the TLS private key is + rotated. +- Existing installations without an active identity pointer remain readable + only as the initial legacy generation. + +## Steps + +1. Write every identity into an owner-only UUID generation directory containing + credential, Ed25519 key, TLS certificate/private key, CA, and a manifest of + exact hashes. Sync every file and directory before it is selectable. +2. Publish one versioned `active-identity-v1.json` pointer atomically. Once the + pointer exists, startup loads only that generation and never falls back to or + combines legacy files. +3. Persist renewal operation, old pointer, new pointer, and fresh TLS key before + switching. On restart, reconcile the observed pointer instead of guessing + whether a rename completed. +4. Restart the real user service and require its durable runtime-ready record + plus a new-identity mTLS health request. Roll back the pointer and restart the + old runtime only while the old credential is still valid. +5. After confirmed adoption, delete only an inactive, manifest-validated exact + generation allowlist through one pinned parent/generation descriptor pair. + Manifest names and directory entries must equal the exact required set; + every material, including the Ed25519 key, is decoded from its bounded, + hash-checked descriptor-relative read. Cleanup compares the parent entry's + device/inode before `unlinkat(AT_REMOVEDIR)`. Unknown entries, replacement, + or durability uncertainty retain the generation and emit a repair warning. +6. Apply the same exact allowlist to destructive purge; always retain Domain + Root, founding Authority, invitation/audit administration, and revocation + authority material. +7. Persist the exact signed departure request before its first network attempt. + A later `node leave` reuses its operation ID and envelope even after the + local phase is `Left`. Persist and sync the Directory-signed receipt before + clearing pending state; pending plus receipt is a recoverable crash state. +8. Serialize credential renewal and destructive purge with one owner-only, + nonblocking `identity-operation-v1.lock` held across the complete lifecycle + workflow, including service adoption and retired cleanup. HostRuntime never + takes this high-level lock. +9. Use the owner-only `identity-generations/` directory descriptor as the + equivalent active-pointer read/write lock: startup/load holds a short shared + `flock`; pointer publish/rollback, inactive cleanup, and purge hold an + exclusive lock. Cleanup re-reads the active pointer after locking and keeps + the lock through fd-relative unlink and sync. Service restart starts only + after the pointer writer releases the exclusive lock. + +## Acceptance criteria + +- Every crash before pointer publication loads the complete old identity; + every crash after publication loads the complete new identity; no loader path + can return a mixed bundle. +- Traversal, symlink, foreign owner/mode, hash mismatch, unknown cleanup entry, + active-generation selection, directory replacement, and pointer or sync + uncertainty fail closed. Tests replace each material independently and race + cleanup against an attacker substitute. +- Offline departure reaches local `Left` with `recorded=false,pending=true`; + a later online retry sends the identical signed request, validates the exact + Directory signer and request binding, persists the receipt, and clears the + pending record. Missing or tampered receipt state is never reported recorded. +- Concurrent retired publication blocks behind cleanup and then fails because + the retired generation no longer exists; a shared reader blocks cleanup until + its complete generation is decoded. A second renewal gets a stable busy + result, including across processes, and an abruptly exited lock holder does + not poison restart. +- Successful renewal proves that the platform service adopted the new + generation. Cleanup uncertainty is a warning and never a false deletion + claim. +- Task 13 focused tests, full Rust gates, secret scan, and ignored evidence are + complete before Task 14 begins. + +## Risks + +- Service-manager restart is platform dependent. An unavailable user session is + a typed incomplete rotation, not simulated readiness. +- Directory registration is repeated by the restarted HostRuntime; this plan + does not add a second registration mechanism to the lifecycle CLI. +- Physical overlay verification remains Task 14 and cannot be inferred from + loopback mTLS evidence. +- `flock` is an advisory same-user coordination boundary. Generation + directories remain `0700`, and every child access is `openat`/`unlinkat` + relative with `O_NOFOLLOW`, regular-file, owner, and mode checks. A malicious + process already running as the same effective UID is outside the v0.2 threat + boundary and can ignore advisory locks or replace leaf entries. diff --git a/plan/01-v4-multi-host-node-bootstrap.md b/plan/01-v4-multi-host-node-bootstrap.md new file mode 100644 index 0000000..1416be8 --- /dev/null +++ b/plan/01-v4-multi-host-node-bootstrap.md @@ -0,0 +1,86 @@ +# AgenNet Task 14 preflight revision — production physical pursuit + +## Goal + +Close the production-path gap found before the physical two-device gate. The +founding host must run a signed Directory and Requester without embedding a +provider endpoint, and an operator must be able to submit one real LLM-decided +source-metrics pursuit through a loopback-only local control plane. + +## Preconditions + +- Tasks 1–13 are complete at commit `ff8e097`. +- Invitation, enrollment, Node Credential, configuration, and journal formats + remain v4/v0.4/v0.3/schema 2 unless a persisted consumer actually changes. +- The loopback demo retains four least-privilege credentials and is never + evidence for the physical gate. + +## Steps + +1. Issue the founding credential with exactly `Directory` and `Requester` roles + and an empty provider capability ceiling. Generate an owner-only 32-byte + local-control token; never expose it through output, argv, environment, or + logs. Provider join does not create this token. +2. Merge the founding Directory peer routes with the Requester's signed peer + Artifact routes on the exact private-overlay mTLS listener. Run pursuit + control on a separate dynamic loopback listener and publish only an + owner-only, versioned ready record without the token. +3. Add `agenet pursuit run --env-file --artifact --output json`. + The CLI reads only the three model variables, makes the strict real model + decision with one repair and no fallback, and submits the decision plus + bounded Artifact bytes to the loopback control listener. It cannot accept a + Directory or provider endpoint. +4. Bind local requests to loopback connection metadata and compare the bearer + in constant time. Reject missing/wrong tokens, oversized Artifacts, invalid + decisions, duplicate operation conflicts, and non-loopback callers before + effects. Remove the ready record and reap the listener on shutdown. +5. Exercise the real production HostRuntime, captured fake LLM server, signed + Manifest discovery, two bilateral Contracts, independent verification, and + final `Accepted` before asking for physical devices. +6. Serialize invitation create/reserve/consume/release across the CLI and + long-lived Authority with one owner-only process lock. Refresh and bounded + replay the pinned checksummed journal under that lock before mutation, so a + live CLI-created invitation is visible without restarting the Authority. +7. Pin both the invitation operation lock and journal inode for the store + lifetime. Validate lock pathname continuity after bounded acquisition and + immediately before reload or append; hold the journal inode's exclusive + lock across replay, validation, append, flush, and sync. +8. Require evidence collection to observe a running platform service on both + devices and self-probe each exact configured peer endpoint over its + validated local mTLS identity. Exact-match public Node/process identity, + readiness, and current revocation state to credential and service metadata. + Device A additionally probes the loopback local-control health response and + matches the same ready generation. +9. Lock the already-open invitation journal inode during initial replay. An + opener must wait for a concurrent framed append to finish or return a stable + bounded lock error; it must never parse a half-frame. + +## Acceptance criteria + +- The founding persisted credential verifies exactly the Directory and + Requester roles; its provider capability ceiling remains empty. +- Provider state contains no control token and exposes no local control + listener. +- The Requester begins with signed Directory seeds only. Executor and Verifier + endpoints are learned only from signed manifests over mTLS. +- Public pursuit output contains only IDs, Artifact hash/metrics, stages, model + call count, and final state. It contains no prompt, path, IP, token, key, + credential, or authorization header. +- Automated loopback/mTLS tests prove protocol behavior only. Task 14 remains + incomplete until the same path passes on two physical private-overlay hosts. +- A real CLI process may create an invitation after Authority startup and a + provider may redeem it immediately; concurrent create/redeem, lock-holder + crash, stale projection, corruption, and pathname replacement fail safely. +- Replacing a regular owner-only operation-lock pathname cannot split the + journal write domain, and initial replay cannot observe a half-frame. A dead + peer or local listener, stale metadata/revocation, redirect, oversized + response, or mismatched TLS Node/process cannot produce an evidence fragment. + +## Risks + +- The local-control token is not yet rotatable; deletion disables pursuits and + rotation is deferred to a versioned lifecycle command. +- The first production pursuit CLI is intentionally source-metrics-specific. + It is not an arbitrary Agent or shell interface. +- Model availability and an interactive user service session remain operational + prerequisites for the physical run. diff --git a/plan/02-v1-installation-surfaces.md b/plan/02-v1-installation-surfaces.md new file mode 100644 index 0000000..bb05301 --- /dev/null +++ b/plan/02-v1-installation-surfaces.md @@ -0,0 +1,437 @@ +# AgenNet Installation Surfaces Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use +> `superpowers:subagent-driven-development` (recommended) or +> `superpowers:executing-plans` to implement this plan task-by-task. Steps use +> checkbox (`- [ ]`) syntax for tracking. Use `skill-creator` for the Codex Skill +> task and validate the generated package with its bundled validator. + +**Goal:** Let a human, a general coding Agent, or a non-Agent machine install a +fixed AgenNet release and enter the same secure CLI-controlled enrollment flow, +with reproducible release metadata, checksums, GitHub artifact attestations, and +no secret exposure through documentation or automation. + +**Architecture:** Publish four native archives and one signed release manifest. +Keep installation separate from enrollment: the convenience installer only +selects and verifies a binary, while `agenet node join` owns all state changes +and TTY-only secret input. Generate the human guide, raw Agent guide, Codex Skill +reference, and website bootstrap metadata from versioned repository sources so +their commands and release identifiers cannot drift. + +**Tech Stack:** GitHub Actions, GitHub Releases, GitHub artifact attestations, +POSIX shell, Rust release-manifest generator, Codex Skill package, macOS/Linux +arm64/x86_64. + +## Global Constraints + +- Start only after Tasks 1–13 of + `plan/01-v1-multi-host-node-bootstrap.md` pass locally. Release candidates may + precede the physical gate, but public copy must remain Developer Preview until + Task 14 passes. +- The installer never accepts an invitation, passphrase, API key, Agent token, + or private-network credential. It installs only a verified executable. +- Never use a moving branch archive, `latest` URL in Agent instructions, + unpinned Action, or manifest whose version differs from the request. +- The repository owns the canonical guides, schema, and Skill. The website + consumes generated copies and must not fork them. +- Keep the Skill concise, imperative, under 500 lines, and delegate every state + mutation to `agenet`. +- A coding Agent may inspect diagnostics, but installs an Agent adapter only + after a second explicit authorization and never sees the invitation in chat. +- Use the five-section Commit Message format for each reviewable task. + +--- + +## Task 1: Define and generate the release manifest + +**Files:** + +- Create: `src/release/mod.rs` +- Create: `src/release/manifest.rs` +- Modify: `src/lib.rs` +- Create: `src/bin/agenet-release-manifest.rs` +- Create: `schemas/release-manifest-v1.schema.json` +- Create: `tests/release_manifest.rs` +- Modify: `Cargo.toml` + +**Interfaces:** + +```rust +pub struct ReleaseManifestV1 { + pub schema_version: u32, + pub project: String, + pub version: String, + pub git_commit: String, + pub published_at: String, + pub minimum_rust_version: String, + pub artifacts: BTreeMap, +} + +pub struct ReleaseArtifact { + pub file_name: String, + pub download_url: String, + pub sha256: String, + pub size_bytes: u64, + pub attestation_subject: String, +} + +pub enum ReleaseTarget { + MacosArm64, + MacosX86_64, + LinuxArm64, + LinuxX86_64, +} +``` + +- [ ] Write failing tests for a complete four-target manifest, missing target, + duplicate filename, invalid SHA-256, wrong display name, non-SemVer version, + non-HTTPS URL, URL/version mismatch, unexpected host, path traversal, + unstable JSON ordering, and JSON Schema parity. +- [ ] Implement strict Serde types with `deny_unknown_fields` and deterministic + `BTreeMap` serialization. Accept release URLs only under + `https://github.com/Nexa-Language/AgenNet/releases/download/v/`. +- [ ] Implement `agenet-release-manifest` to read explicit archive paths, + calculate checksums and sizes, require explicit version and full commit SHA, + and write atomically without network access. +- [ ] Generate and validate the checked-in JSON Schema; make the test fail when + the Rust model and schema differ. +- [ ] Run `cargo test --test release_manifest` and + `cargo run --bin agenet-release-manifest -- --help`. +- [ ] Commit: + +```text +[feat][Release][1/7] Define release manifest + +Root cause: NA +Solution: Add a deterministic four-platform manifest shared by +installers and the public site. +Risks: A future target requires a schema-versioned manifest change. +Dependency: Bootstrap plan tasks 1-13. +Links: plan/02-v1-installation-surfaces.md +``` + +## Task 2: Build reproducible four-target release archives + +**Files:** + +- Create: `.github/workflows/release.yml` +- Create: `scripts/package-release.sh` +- Create: `scripts/check-release-archive.sh` +- Create: `tests/scripts/test-release-archive.sh` +- Create: `packaging/README.release.md` +- Create: `LICENSE` + +**Archive contract:** + +```text +agenet-v-/ +├── agenet +├── LICENSE +├── README.md +└── RELEASE-METADATA.json +``` + +- [ ] Write the archive checker and failing fixtures first. Reject absolute or + parent-relative entries, symlinks, unexpected files, bad executable mode, + mismatched binary version/commit, failed smoke test, and unstable ordering. +- [ ] Implement `scripts/package-release.sh` with explicit `--binary`, + `--target`, `--version`, `--commit`, and `--output-dir`. Normalize timestamps, + uid/gid, ordering, and permissions without mutating the built binary. +- [ ] Add the repository's declared MIT license text as `LICENSE` and include it + byte-for-byte in every archive. +- [ ] Create a tag-triggered native matrix using `macos-15` for arm64, + `macos-15-intel` for x86_64, `ubuntu-24.04-arm` for arm64, and + `ubuntu-24.04` for x86_64. Do not hide emulation behind one job. +- [ ] Pin every third-party Action to a full commit SHA with its upstream tag in + a comment. Keep permissions read-only until the publish job. +- [ ] In every job run format, Clippy, target-appropriate tests, release build, + `agenet --version`, archive validation, and exact artifact upload. +- [ ] Run the packager/checker locally, lint the workflow with `actionlint`, and + inspect the archive using `tar -tvf`. +- [ ] Commit: + +```text +[feat][Release][2/7] Build native archives + +Root cause: NA +Solution: Build deterministic native archives on four stable GitHub +runners and validate their contents. +Risks: Runner image updates can alter linked system-library behavior. +Dependency: Release step 1. +Links: packaging/README.release.md +``` + +## Task 3: Publish checksums and GitHub artifact attestations + +**Files:** + +- Modify: `.github/workflows/release.yml` +- Create: `scripts/verify-release.sh` +- Create: `tests/scripts/test-verify-release.sh` +- Modify: `packaging/README.release.md` + +**Verification contract:** + +```text +scripts/verify-release.sh \ + --version \ + --archive \ + --manifest \ + [--require-gh-attestation] +``` + +- [ ] Write tests first for valid input, bad checksum, wrong target/version, + alternate origin, absent attestation tool, and failed mocked attestation. +- [ ] Implement checksum verification with `shasum -a 256` or `sha256sum`, exact + size comparison, archive validation, and binary version verification. +- [ ] Make high-assurance mode require + `gh attestation verify --repo Nexa-Language/AgenNet`; missing `gh`, + offline verification, and failed attestation are hard errors. +- [ ] Generate `SHA256SUMS` and the manifest, attest each archive and manifest, + then publish one immutable release for the exact annotated tag. +- [ ] Grant `id-token: write`, `attestations: write`, and `contents: write` only + to the attestation/publish job. Everything else keeps `contents: read`. +- [ ] Reject tag/Cargo version mismatch, an existing release, a dirty generated + manifest, or a tag commit not reachable from `master`. +- [ ] Run shell tests and workflow validation. After publishing a candidate, + download it into a clean temporary directory and verify both modes. +- [ ] Commit: + +```text +[feat][Release][3/7] Attest release artifacts + +Root cause: NA +Solution: Publish fixed-version checksums, a strict manifest, and GitHub +artifact attestations with least-privilege permissions. +Risks: High assurance depends on GitHub's service and gh CLI. +Dependency: Release step 2. +Links: plan/02-v1-installation-surfaces.md +``` + +## Task 4: Implement the convenience installer without enrollment logic + +**Files:** + +- Create: `scripts/install.sh` +- Create: `tests/scripts/test-install.sh` +- Create: `tests/fixtures/install/manifest.json` +- Modify: `README.md` + +**Installer contract:** + +```text +sh install.sh --version [--prefix ] + [--manifest-url ] +``` + +- [ ] Write a hermetic test harness with a local fixture server and fake + `uname`. Cover all four mappings, unsupported targets, TLS failure, redirect, + oversized/invalid manifest, bad checksum, partial download, existing binary, + unwritable prefix, interrupted atomic install, and cleanup. +- [ ] Implement strict parsing with `set -eu`, explicit SemVer, absolute prefix, + bounded HTTPS downloads, `mktemp -d`, and cleanup traps. +- [ ] Select only from the validated manifest, verify checksum and archive before + extraction, run `agenet --version`, and atomically replace only + `/bin/agenet`. +- [ ] Default to a user-owned prefix such as `~/.local`; never run `sudo`. Print + a safe explicit alternative if the prefix is not writable. +- [ ] Print `agenet node doctor` and `agenet node join` as next steps. Do not + prompt + for an invitation or install a service. +- [ ] Document inspected convenience installation and the downloaded + high-assurance attestation path separately. +- [ ] Run `shellcheck`, the shell suite, and install into a temporary prefix with + no writes outside that prefix. +- [ ] Commit: + +```text +[feat][Install][4/7] Add verified installer + +Root cause: NA +Solution: Install one fixed, checksummed binary atomically while keeping +enrollment and service state inside the CLI. +Risks: Convenience mode omits attestations without high assurance. +Dependency: Release step 3. +Links: README.md +``` + +## Task 5: Create versioned human and generic Agent guides + +**Files:** + +- Create: `docs/bootstrap/node-setup.md` +- Create: `docs/bootstrap/agent-node-setup.md` +- Create: `docs/bootstrap/agent-node-setup.zh-CN.md` +- Create: `docs/bootstrap/agent-contract-v1.schema.json` +- Create: `src/bin/render-bootstrap-docs.rs` +- Create: `tests/bootstrap_docs.rs` +- Modify: `README.md` + +**Agent execution contract:** + +```json +{ + "schema_version": 1, + "phase": "preflight|installed|awaiting_secret|joined|service_ready|complete", + "requires_user_action": true, + "safe_next_command": "agenet node join ...", + "diagnostics": [], + "secret_received": false +} +``` + +- [ ] Write tests that parse every fenced command, compare CLI flags with Clap's + command model, validate JSON examples, and reject invitation-shaped strings, + example private keys, shell interpolation, moving URLs, unpinned versions, or + unsupported commands. +- [ ] Write the human guide from preflight through verified install, overlay + check, join, service install, doctor, adapter authorization, leave, and + uninstall. Keep destructive and secret steps visibly operator-owned. +- [ ] Write concise imperative Agent guides. Require the Agent to inspect the + platform and overlay, install a fixed verified version, stop at + `awaiting_secret`, direct the user to AgenNet's hidden TTY prompt, resume from + sanitized JSON, and request another authorization before adapter installation. +- [ ] State that invitations never enter chat, prompts, args, env, Agent-created + files, ordinary stdin, or logs. The Agent stops instead of inventing a bypass. +- [ ] Implement a deterministic renderer that checks command blocks against + stored `agenet --help` snapshots and writes website-consumable copies under + `site/generated/` only when explicitly invoked. +- [ ] Add sentinel invitation and API-key tests across every documented error + path; neither sentinel may appear in generated output. +- [ ] Run `cargo test --test bootstrap_docs`, then exercise both guides in a + clean Agent context and verify they stop before secret input. +- [ ] Commit: + +```text +[doc][Install][5/7] Add bootstrap guides + +Root cause: NA +Solution: Define human and machine-checkable Agent workflows that stop +at the TTY-only invitation boundary. +Risks: Untested Agent clients may format progress differently. +Dependency: Install step 4. +Links: docs/bootstrap/agent-node-setup.md +``` + +## Task 6: Package and install the Codex Skill + +**Files:** + +- Create: `skills/agenet-node-bootstrap/SKILL.md` +- Create: `skills/agenet-node-bootstrap/agents/openai.yaml` +- Create: `skills/agenet-node-bootstrap/references/node-setup.md` +- Create: `src/cli/agent.rs` +- Modify: `src/cli/mod.rs` +- Create: `tests/skill_package.rs` +- Create: `tests/agent_cli.rs` + +**Command contract:** + +```text +agenet agent install-skill --target codex +agenet agent inspect-skill --target codex +agenet agent uninstall-skill --target codex +``` + +- [ ] Read the Skill Creator `references/openai_yaml.md`, then initialize + `skills/agenet-node-bootstrap` with `init_skill.py`; remove all generated + placeholders and do not create auxiliary README or changelog files. +- [ ] Write package tests first: require lowercase hyphenated naming, exactly + `name` and `description` in frontmatter, imperative body below 500 lines, + valid UI metadata, one-level references, no duplicated guide, no enrollment + scripts, no secrets, and only supported AgenNet commands. +- [ ] Trigger the Skill for install, configure, join, diagnose, leave, or remove + requests. Its flow is inspect → verify binary → safe preflight → user TTY + action → sanitized status → optional adapter authorization. +- [ ] Generate `references/node-setup.md` from the canonical Agent guide. The + Skill loads it only for setup or recovery and contains no copied long-form + explanation. +- [ ] Generate `agents/openai.yaml` deterministically with `display_name`, + `short_description`, and `default_prompt`; do not add unapproved icons or + brand colors. +- [ ] Implement Skill installation to resolve the Codex skills directory, + compare package hash/version, show exact target files, require confirmation, + and copy atomically. Never overwrite a modified Skill without backup and + explicit confirmation; never enroll a node or install an adapter. +- [ ] Implement inspect/uninstall using an AgenNet-owned receipt. Remove only + files whose hashes match the receipt; preserve modifications and return + `SkillModifiedByUser`. +- [ ] Run Skill Creator `quick_validate.py`, both Rust tests, installation into + an isolated temporary Codex home, and forward-test fresh install, + already-enrolled doctor, and safe leave without a live invitation. +- [ ] Commit: + +```text +[feat][Agent][6/7] Package bootstrap skill + +Root cause: NA +Solution: Package a validated Codex Skill using the canonical guide and +delegates every mutation to the AgenNet CLI. +Risks: Codex discovery paths may require a new target adapter. +Dependency: Install step 5. +Links: skills/agenet-node-bootstrap/SKILL.md +``` + +## Task 7: Seal the release and installation matrix + +**Files:** + +- Create: `docs/testing/release-install-matrix.md` +- Create: `docs/testing/release-install-matrix.json` +- Create: `scripts/verify-install-matrix.sh` +- Modify: `.github/workflows/release.yml` +- Modify: `README.md` +- Modify: `ROADMAP.md` +- Modify: `docs/design/agenet-v0.1.md` + +- [ ] Define a machine-readable matrix for macOS arm64/x86_64 and Linux + arm64/x86_64. Each row requires artifact verification, smoke test, installer, + doctor, join dry-run to the TTY boundary, service rendering, Skill lifecycle, + and cleanup. +- [ ] Implement the matrix verifier to reject missing rows, release mismatch, + absent attestations, skipped checks, writes outside temporary/user roots, or + secret-shaped output. +- [ ] Extend release CI to run non-secret matrix checks natively on all four + runners before publish. Keep real enrollment in the physical-device gate. +- [ ] Publish one release candidate from a clean commit reachable from `master`, + then test both verification paths in clean macOS and Linux environments. +- [ ] Prove the Agent guide and Skill resolve the same pinned version, emit the + same CLI sequence, stop at the same secret boundary, and report the same + sanitized phases. +- [ ] Run all Rust gates, shellcheck, shell tests, Skill validation, + manifest/schema tests, workflow lint, and repository secret scanning. +- [ ] Record the exact tag, commit, matrix result, known failures, and deferred + targets in ROADMAP. Do not describe the candidate as stable. +- [ ] Commit: + +```text +[milestone][Install][7/7] Verify install surfaces + +Root cause: NA +Solution: Validate artifacts, provenance, installer, Agent guide, and +Codex Skill across the supported platform matrix. +Risks: Real enrollment remains subject to the physical-device gate. +Dependency: Install step 6 and a published release candidate. +Links: docs/testing/release-install-matrix.md +``` + +## Final Acceptance Commands + +```bash +cargo fmt --check +cargo clippy --all-targets --all-features -- -D warnings +cargo test --all-targets +shellcheck scripts/install.sh scripts/package-release.sh \ + scripts/check-release-archive.sh scripts/verify-release.sh \ + scripts/verify-install-matrix.sh +tests/scripts/test-release-archive.sh +tests/scripts/test-verify-release.sh +tests/scripts/test-install.sh +scripts/verify-install-matrix.sh \ + docs/testing/release-install-matrix.json +``` + +Installation surfaces are incomplete if any path selects a moving version, +duplicates enrollment logic, accepts the invitation outside a hidden TTY, +overwrites a modified Skill, bypasses verification, or gives humans and Agents +different CLI semantics. diff --git a/plan/02-v2-installation-surfaces.md b/plan/02-v2-installation-surfaces.md new file mode 100644 index 0000000..15ec061 --- /dev/null +++ b/plan/02-v2-installation-surfaces.md @@ -0,0 +1,502 @@ +# AgenNet Preview Installation Surfaces Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use +> `superpowers:subagent-driven-development` (recommended) or +> `superpowers:executing-plans` to implement this plan task-by-task. Steps use +> checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Publish one immutable `v0.2.0-preview.1` release that a human or an +Agent inside WSL2 can install with one verified command without exposing +enrollment secrets. + +**Architecture:** A strict Rust release manifest describes four native +archives. GitHub Actions builds, attests, and publishes those archives; a +generated fixed-version POSIX installer selects and verifies one archive. Raw +Agent guides and the `agenet-node-bootstrap` Skill delegate all mutations to +the installed CLI and stop at controlling-TTY secret boundaries. + +**Tech Stack:** Rust 1.97.1, Serde, JSON Schema, POSIX shell, GitHub Actions, +GitHub Releases and artifact attestations, macOS arm64/x86_64, Linux/WSL2 +arm64/x86_64, Codex Agent Skills. + +## Global Constraints + +- Release exactly `v0.2.0-preview.1`; never use `latest` or a moving branch. +- Display name is **AgenNet**; executable, crate, and paths use `agenet`. +- Native Windows is unsupported; WSL2 consumes a Linux artifact. +- The installer installs only the binary and never accepts an Invitation, + passphrase, API key, signing key, TLS key, token, or overlay credential. +- Install under `~/.local/bin` without `sudo` or system-wide mutation. +- Existing different binaries are preserved unless the human explicitly + approves replacement. +- Public copy says **Developer Preview — physical acceptance pending**. +- Every production behavior follows RED → GREEN → REFACTOR. +- All third-party Actions are pinned to full commit SHAs. +- Commit messages use the repository five-section format. + +--- + +## Task 1: Define the strict release manifest + +**Files:** + +- Create: `src/release/mod.rs` +- Create: `src/release/manifest.rs` +- Create: `src/bin/agenet-release-manifest.rs` +- Create: `schemas/release-manifest-v1.schema.json` +- Create: `tests/release_manifest.rs` +- Modify: `src/lib.rs` +- Modify: `Cargo.toml` + +**Interfaces:** + +- Produces: + +```rust +pub const PREVIEW_VERSION: &str = "0.2.0-preview.1"; + +pub enum ReleaseTarget { + MacosArm64, + MacosX86_64, + LinuxArm64, + LinuxX86_64, +} + +pub struct ReleaseArtifact { + pub file_name: String, + pub download_url: String, + pub sha256: String, + pub size_bytes: u64, + pub attestation_subject: String, +} + +pub struct ReleaseManifestV1 { + pub schema_version: u32, + pub project: String, + pub version: String, + pub git_commit: String, + pub published_at: String, + pub artifacts: BTreeMap, +} +``` + +- Consumers: archive publisher, installer renderer, Agent guide generator, and + Sites data synchronizer. + +- [ ] **Step 1: Write manifest behavior tests** + +Add tests for a complete four-target manifest and rejection of an unknown +field, missing target, duplicate filename, invalid SHA-256, short commit SHA, +wrong project spelling, non-SemVer version, non-HTTPS URL, non-GitHub host, +version/path mismatch, traversal, and unstable key ordering. + +```rust +#[test] +fn rejects_release_url_for_a_different_version() { + let mut manifest = valid_manifest(); + manifest.artifacts.get_mut(&ReleaseTarget::LinuxX86_64) + .unwrap().download_url = + "https://github.com/Nexa-Language/AgenNet/releases/download/v0.2.0/agenet.tar.gz".into(); + assert_eq!(manifest.validate(), Err(ReleaseError::VersionUrlMismatch)); +} +``` + +- [ ] **Step 2: Run RED** + +Run: + +```bash +cargo test --test release_manifest +``` + +Expected: compilation fails because `agenet::release` does not exist. + +- [ ] **Step 3: Implement the minimal strict model and offline generator** + +Use `deny_unknown_fields`, deterministic `BTreeMap` serialization, exact +GitHub release URL validation, bounded archive metadata, lowercase 64-character +SHA-256, and atomic output. The CLI takes explicit archive paths, version, +commit, publication time, and output path; it performs no network requests. + +- [ ] **Step 4: Verify GREEN and schema parity** + +Run: + +```bash +cargo test --test release_manifest +cargo run --bin agenet-release-manifest -- --help +cargo fmt --check +cargo clippy --bin agenet-release-manifest --test release_manifest -- -D warnings +``` + +Expected: all pass; two identical generator runs are byte-for-byte equal. + +- [ ] **Step 5: Commit** + +```text +[feat][Release][1/6] Define preview manifest + +Root cause: NA +Solution: Add a strict deterministic four-platform release manifest. +Risks: New targets require a schema-versioned manifest revision. +Dependency: Host preview commit 7db2556. +Links: plan/02-v2-installation-surfaces.md +``` + +## Task 2: Build reproducible native archives + +**Files:** + +- Create: `LICENSE` +- Create: `scripts/package-release.sh` +- Create: `scripts/check-release-archive.sh` +- Create: `tests/scripts/release-archive.sh` +- Create: `.github/workflows/release.yml` +- Create: `packaging/release.md` + +**Interfaces:** + +- Consumes: `ReleaseTarget` names and `PREVIEW_VERSION` from Task 1. +- Produces exactly these archives: + +```text +agenet-v0.2.0-preview.1-aarch64-apple-darwin.tar.gz +agenet-v0.2.0-preview.1-x86_64-apple-darwin.tar.gz +agenet-v0.2.0-preview.1-aarch64-unknown-linux-gnu.tar.gz +agenet-v0.2.0-preview.1-x86_64-unknown-linux-gnu.tar.gz +``` + +Each archive contains exactly: + +```text +agenet-v0.2.0-preview.1-{rust_target}/ +├── agenet +├── LICENSE +├── README.md +└── RELEASE-METADATA.json +``` + +- [ ] **Step 1: Write failing archive fixtures and checker tests** + +The shell test creates valid and invalid archives. Reject absolute paths, +`../`, symlinks, unexpected files, non-executable binary mode, non-regular +members, version/commit mismatch, unsafe owner metadata, and unstable ordering. + +```bash +if scripts/check-release-archive.sh "$fixture/traversal.tar.gz"; then + echo "traversal archive was accepted" >&2 + exit 1 +fi +``` + +- [ ] **Step 2: Run RED** + +Run `bash tests/scripts/release-archive.sh`. + +Expected: fail because packager and checker do not exist. + +- [ ] **Step 3: Implement deterministic packaging** + +Require explicit `--binary`, `--target`, `--version`, `--commit`, and +`--output-dir`. Normalize timestamps, uid/gid, member order, directory mode, +and file modes. Never mutate the source binary. Smoke-test the staged binary +with `--version` before archiving. + +- [ ] **Step 4: Add the native GitHub matrix** + +Use native GitHub runners for the four targets. Before writing the workflow, +resolve each official Action tag to a full commit SHA using its upstream +repository and record the tag in a comment. Jobs run format, Clippy, +target-appropriate tests, release build, binary version check, archive checker, +and artifact upload. The publish job alone receives `contents: write` and +`id-token: write` for attestations. + +- [ ] **Step 5: Verify GREEN** + +Run: + +```bash +bash tests/scripts/release-archive.sh +bash scripts/package-release.sh --help +actionlint .github/workflows/release.yml +cargo test --all-targets +``` + +Expected: archive tests and Rust tests pass; `tar -tvf` shows only the exact +four-member contract with normalized metadata. + +- [ ] **Step 6: Commit** + +```text +[feat][Release][2/6] Build native preview archives + +Root cause: NA +Solution: Add deterministic native packaging and a pinned release matrix. +Risks: Native runner availability can delay one platform artifact. +Dependency: Release manifest step 1. +Links: packaging/release.md +``` + +## Task 3: Publish checksums, attestations, and generated installer + +**Files:** + +- Create: `src/bin/agenet-render-installer.rs` +- Create: `scripts/install.sh.template` +- Create: `scripts/verify-release.sh` +- Create: `tests/installer.rs` +- Create: `tests/scripts/installer-smoke.sh` +- Modify: `.github/workflows/release.yml` + +**Interfaces:** + +- Consumes: a validated `ReleaseManifestV1` and the four archives. +- Produces: `install.sh`, `SHA256SUMS`, manifest, artifact attestations, and a + draft GitHub Release before final publication. + +- [ ] **Step 1: Write installer rendering and shell behavior tests** + +Assert exact embedded version/target/checksum entries. Test macOS/Linux/WSL2 +selection, unsupported native Windows signatures, unknown architecture, +checksum mismatch, unsafe archive, interrupted staging, same-version +idempotency, different-binary preservation, unwritable destination, and PATH +instructions. Feed secret-shaped sentinels and prove stdout/stderr do not echo +them and no enrollment command is run. + +```rust +#[test] +fn rendered_installer_contains_no_moving_release_url() { + let text = render_installer(&valid_manifest()).unwrap(); + assert!(text.contains("/download/v0.2.0-preview.1/")); + assert!(!text.contains("/latest/")); + assert!(!text.contains("/heads/")); +} +``` + +- [ ] **Step 2: Run RED** + +Run: + +```bash +cargo test --test installer +bash tests/scripts/installer-smoke.sh +``` + +Expected: missing renderer and generated installer failures. + +- [ ] **Step 3: Implement the generated installer** + +Generate a POSIX shell script with immutable archive URLs and checksums. Use a +private temporary directory, HTTPS-only curl settings, bounded downloads, +`sha256sum` or `shasum -a 256`, archive preflight, a same-directory temporary +binary, version smoke test, and atomic rename into `~/.local/bin`. Do not add an +environment override for release hosts or secret inputs. + +- [ ] **Step 4: Finish GitHub publication** + +The tag workflow builds all archives, generates and verifies the manifest, +renders the installer, generates `SHA256SUMS`, creates attestations, uploads all +public assets, and publishes a prerelease only if the exact four-target set is +complete. Re-running the same tag must compare assets and fail on divergence. + +- [ ] **Step 5: Verify GREEN** + +Run focused tests twice, shellcheck the scripts, scan output for sentinels, and +perform clean-prefix installs on macOS plus a Linux container that represents +the WSL filesystem/user boundary without claiming physical WSL acceptance. + +- [ ] **Step 6: Commit** + +```text +[feat][Release][3/6] Generate verified installer + +Root cause: NA +Solution: Render one immutable checksum-verifying user installer. +Risks: A missing platform checksum blocks the complete prerelease. +Dependency: Native archives step 2. +Links: plan/02-v2-installation-surfaces.md +``` + +## Task 4: Publish canonical human and Agent guides + +**Files:** + +- Create: `docs/install/index.md` +- Create: `docs/install/index.en.md` +- Create: `docs/bootstrap/agent-node-setup.md` +- Create: `docs/bootstrap/agent-node-setup.en.md` +- Create: `src/bin/agenet-sync-public-guides.rs` +- Create: `tests/public_guides.rs` + +**Interfaces:** + +- Consumes: validated release manifest and public CLI help output. +- Produces: bilingual human guides, raw Agent guides, and normalized generated + copies for the Sites project. + +- [ ] **Step 1: Write guide contract tests** + +Reject moving URLs, version mismatch, native-Windows claims, `sudo`, secret +arguments/env/stdin, local absolute paths, private IPs, test sentinels, command +drift, missing WSL2/systemd diagnostics, and missing physical-pending status. + +- [ ] **Step 2: Run RED** + +Run `cargo test --test public_guides` and confirm missing canonical guides. + +- [ ] **Step 3: Write the minimal canonical guides and synchronizer** + +The Agent guide is imperative and bounded. It installs, checks public state, +and stops before `node join` so the human uses the local controlling TTY. It +never asks the user to paste an Invitation into chat. The synchronizer consumes +the manifest and CLI help, normalizes line endings, and writes atomically. + +- [ ] **Step 4: Verify GREEN and idempotency** + +Run the synchronizer twice and require a clean diff, then run guide tests and a +secret/path scan. + +- [ ] **Step 5: Commit** + +```text +[doc][Release][4/6] Publish bootstrap guides + +Root cause: NA +Solution: Generate bilingual human and Agent guides from release truth. +Risks: CLI changes intentionally break the guide parity gate. +Dependency: Verified installer step 3. +Links: docs/bootstrap/agent-node-setup.md +``` + +## Task 5: Create and pressure-test the bootstrap Skill + +**Files:** + +- Create: `skills/agenet-node-bootstrap/SKILL.md` +- Create: `skills/agenet-node-bootstrap/agents/openai.yaml` +- Create: `skills/agenet-node-bootstrap/references/public-status.md` +- Create: `skills/agenet-node-bootstrap/scripts/check-public-readiness.sh` +- Create: `tests/skills/agenet-node-bootstrap-scenarios.md` +- Create: `tests/scripts/skill-readiness.sh` + +**Interfaces:** + +- Consumes: canonical Agent guide, fixed release version, and public CLI + diagnostics. +- Produces: a validated Agent Skill package and the exact one-sentence trigger. + +- [ ] **Step 1: Establish RED pressure scenarios without the Skill** + +Run fresh-context scenarios covering time pressure plus: a user pasting an +Invitation into chat, an Agent asking to receive it, use of `latest`, native +Windows, missing systemd, two overlay IPs, checksum failure, existing managed +state, and a request to bypass TTY. Record the exact unsafe or ambiguous +baseline behavior in the scenario artifact. + +- [ ] **Step 2: Initialize the Skill package** + +Use `skill-creator`'s `init_skill.py` with the exact name +`agenet-node-bootstrap`, scripts and references resources, and generated UI +metadata. Delete all placeholders before validation. + +- [ ] **Step 3: Write the minimal Skill and readiness helper** + +The Skill delegates mutation to `agenet`, reads only public diagnostics, pins +`v0.2.0-preview.1`, distinguishes WSL2 from native Windows, and uses this +required secret-boundary response shape: + +```text +Action required in your local terminal: + agenet node join +Do not paste the invitation or terminal output into this chat. +Tell me only whether the command succeeded or the stable public error code. +``` + +The helper checks OS, WSL2, architecture, command provenance, systemd user +availability, Tailscale/WireGuard public readiness, and existing AgenNet state; +it never reads private state files. + +- [ ] **Step 4: Run GREEN and loophole scenarios** + +Run the same fresh scenarios with the Skill, then add variations for a fake +guide URL, an expired invitation, a request to print private config, and an +already healthy node. The Agent must converge on the safe workflow without +inventing commands. + +- [ ] **Step 5: Validate the package** + +Run `quick_validate.py`, the readiness shell tests, word count, metadata parity, +guide parity, executable-mode checks, and scans for placeholders, secrets, +moving URLs, native-Windows claims, and local paths. + +- [ ] **Step 6: Commit** + +```text +[feat][Skill][5/6] Add node bootstrap Skill + +Root cause: NA +Solution: Teach Agents the fixed CLI flow and local TTY secret boundary. +Risks: Agent runtimes differ in Skill discovery and terminal control. +Dependency: Canonical Agent guide step 4. +Links: skills/agenet-node-bootstrap/SKILL.md +``` + +## Task 6: Seal the preview release candidate + +**Files:** + +- Modify: `README.md` +- Modify: `ROADMAP.md` +- Create: `docs/releases/v0.2.0-preview.1.md` +- Create: `scripts/preflight-preview-release.sh` +- Create: `tests/scripts/preview-release-preflight.sh` + +**Interfaces:** + +- Consumes: Tasks 1–5 and the existing host-runtime test gates. +- Produces: a release-ready commit; it does not create the public tag yet. + +- [ ] **Step 1: Write the failing aggregate preflight test** + +Require exact version agreement across Cargo metadata, manifest generator, +installer, guides, Skill, release notes, workflow, and README. Reject dirty +generated files, unpinned Actions, secret-like tracked data, empty evidence, +missing licenses, and a stable/physical-complete claim. + +- [ ] **Step 2: Run RED** + +Run `bash tests/scripts/preview-release-preflight.sh` and record each missing +surface. + +- [ ] **Step 3: Complete public status and release notes** + +Explain exactly what works, WSL2 versus native Windows, installation and +uninstall boundaries, source-metrics limitation, private-overlay prerequisite, +physical-pending status, and how to report a stable public error without logs +or secrets. + +- [ ] **Step 4: Run final GREEN gates** + +Run: + +```bash +cargo fmt --check +cargo clippy --all-targets --all-features -- -D warnings +cargo test --all-targets +cargo test --all-targets --all-features +bash tests/scripts/preview-release-preflight.sh +``` + +Expected: all pass on the exact release candidate with a clean worktree. + +- [ ] **Step 5: Commit** + +```text +[chore][Release][6/6] Seal preview candidate + +Root cause: NA +Solution: Gate one consistent preview candidate across every public surface. +Risks: Physical two-device acceptance remains pending after publication. +Dependency: Release and Skill steps 1-5. +Links: docs/releases/v0.2.0-preview.1.md +``` diff --git a/plan/03-v1-public-site.md b/plan/03-v1-public-site.md new file mode 100644 index 0000000..0d74866 --- /dev/null +++ b/plan/03-v1-public-site.md @@ -0,0 +1,591 @@ +# AgenNet Public Site Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use +> `superpowers:subagent-driven-development` (recommended) or +> `superpowers:executing-plans` to implement this plan task-by-task. Steps use +> checkbox (`- [ ]`) syntax for tracking. Use `frontend-design` for visual-system +> and hero implementation tasks. + +**Goal:** Publish a fast, bilingual AgenNet landing page and documentation site +at GitHub Pages that presents the real Developer Preview, exposes verified +installation metadata, and delivers the approved A3 Field Study atmosphere with +high-quality interactive light ribbons and an ordered responsive point field. + +**Architecture:** Keep the website completely static. Use Astro for the custom +landing pages and Starlight for documentation, with Chinese at the repository +root and English under `/en/`. Render the hero through an isolated WebGL2 canvas +with deterministic shaders, a CSS fallback, reduced-motion behavior, and no +product telemetry. Generate install commands, manifest data, and Agent-guide +downloads from the canonical release/bootstrap sources rather than duplicating +them in page components. + +**Tech Stack:** Node.js 24, pnpm 11.21.0, Astro 7.2.2, Starlight 0.41.7, +TypeScript 7.0.2, native WebGL2/CSS, Playwright 1.62.1, Pagefind through +Starlight, GitHub Pages Actions. + +## Global Constraints + +- Preserve the approved A3 Field Study direction: near-black mineral field, + sparse technical typography, large slow fluid light ribbons, a disciplined + ordered point lattice that bends near the pointer, subtle grid/grain, and a + controlled black fade into content. +- Do not add random star particles, nearest-neighbor connecting lines, generic + glass-card piles, neon rainbow gradients, fake terminals, fake live node + counts, fake throughput, fake customers, or unverifiable scale claims. +- The site has no backend, secrets, invitation form, account system, telemetry, + cookies, live node status, or enrollment state. Every interactive control is + local navigation, language selection, copy, or visual response. +- Installation commands and downloads must name a fixed published version. + Generated content fails the build when the canonical manifest or guide is + absent, invalid, or inconsistent. +- Canvas is progressive enhancement. Semantic content, navigation, installation, + and docs must remain complete when WebGL is absent, scripting fails, motion is + reduced, or the device is low-power. +- Use design tokens for color, typography, spacing, line, surface, and motion. + Do not scatter magic visual values across components. +- Respect the GitHub Pages base path `/AgenNet/` in navigation, assets, canonical + URLs, language alternates, RSS/sitemap output, and test fixtures. +- Use the five-section Commit Message format; deploy only from reviewed `master`. + +--- + +## Task 1: Scaffold the static bilingual Astro/Starlight application + +**Files:** + +- Create: `site/package.json` +- Create: `site/pnpm-lock.yaml` +- Create: `site/astro.config.mjs` +- Create: `site/tsconfig.json` +- Create: `site/src/content.config.ts` +- Create: `site/src/styles/global.css` +- Create: `site/src/pages/index.astro` +- Create: `site/src/pages/en/index.astro` +- Create: `site/src/content/docs/guide/index.mdx` +- Create: `site/src/content/docs/en/guide/index.mdx` +- Create: `site/public/.nojekyll` +- Modify: `.gitignore` + +**Configuration contract:** + +```js +export default defineConfig({ + site: 'https://nexa-language.github.io', + base: '/AgenNet', + integrations: [ + starlight({ + defaultLocale: 'root', + locales: { + root: { label: '简体中文', lang: 'zh-CN' }, + en: { label: 'English', lang: 'en' }, + }, + }), + ], +}); +``` + +- [ ] Create a failing `site/tests/config.test.ts` that asserts exact site/base, + root Chinese locale, English `/en/`, strict TypeScript, no SSR adapter, and no + dependency versions outside the approved pins. +- [ ] Initialize `site/` with pnpm, pin the stated versions exactly, set + `engines.node` to `>=24 <25`, and commit the lockfile. Do not add React, + Tailwind, Three.js, or an animation library. +- [ ] Configure Starlight i18n and content collections so both docs trees build, + while custom Astro landing pages own `/` and `/en/` under the base path. +- [ ] Add semantic minimal pages, skip link, language switch, and a shared global + stylesheet before introducing visual complexity. +- [ ] Run `pnpm --dir site test`, `pnpm --dir site astro check`, and + `pnpm --dir site build`. Inspect `site/dist/index.html` and + `site/dist/en/index.html`. +- [ ] Commit: + +```text +[feat][Site][1/10] Scaffold bilingual site + +Root cause: NA +Solution: Add a pinned Astro and Starlight foundation with Chinese root, +English locale, and the GitHub Pages base path. +Risks: Astro and Starlight upgrades require compatibility review. +Dependency: Installation plan release manifest schema. +Links: plan/03-v1-public-site.md +``` + +## Task 2: Generate site data from canonical release and guide sources + +**Files:** + +- Create: `site/scripts/sync-canonical-content.mjs` +- Create: `site/src/data/release.ts` +- Create: `site/src/data/copy.ts` +- Create: `site/src/content/docs/guide/installation.mdx` +- Create: `site/src/content/docs/en/guide/installation.mdx` +- Create: `site/public/bootstrap/v0.2/agent-bootstrap.md` +- Create: `site/public/bootstrap/v0.2/agent-bootstrap.en.md` +- Create: `site/public/bootstrap/v0.2/manifest.json` +- Create: `site/tests/canonical-content.test.ts` +- Modify: `site/package.json` + +**Generated-data boundary:** + +```ts +export type PublicRelease = Readonly<{ + version: string; + publishedAt: string; + repository: 'Nexa-Language/AgenNet'; + artifacts: ReadonlyArray<{ + target: + | 'aarch64-apple-darwin' + | 'x86_64-apple-darwin' + | 'aarch64-unknown-linux-gnu' + | 'x86_64-unknown-linux-gnu'; + url: string; + sha256: string; + }>; +}>; +``` + +- [ ] Write tests that compare public generated files byte-for-byte with the + canonical release manifest and Agent guides after normalization. Fail on a + moving URL, private path, secret-shaped token, wrong project name, absent + target, non-HTTPS download, or untranslated command semantics. +- [ ] Implement a deterministic sync script that validates the release manifest + against its schema, copies only approved public fields, normalizes line + endings, and refuses to write when the release is unpublished. +- [ ] Keep localized explanatory copy in typed `copy.ts`; keep commands, + versions, artifact URLs, and hashes in generated release data. Components may + not hardcode them. +- [ ] Generate installation MDX from the canonical human guides while preserving + the exact CLI commands and adding locale-specific prose around them. +- [ ] Expose the Chinese raw Agent guide and release manifest at the approved + `/bootstrap/v0.2/agent-bootstrap.md` and `/bootstrap/v0.2/manifest.json` + paths; expose the English raw guide beside them as `agent-bootstrap.en.md`. +- [ ] Add `prebuild` and `check:generated` scripts; CI fails if generation changes + tracked files. +- [ ] Run sync twice to prove idempotency, run tests, and inspect generated files + for local paths, sentinels, and private metadata. +- [ ] Commit: + +```text +[feat][Site][2/10] Sync canonical bootstrap data + +Root cause: NA +Solution: Generate install metadata and Agent guides from validated +canonical repository sources. +Risks: Builds stop when no valid published release exists. +Dependency: Site step 1 and installation plan steps 1-5. +Links: docs/bootstrap/agent-node-setup.md +``` + +## Task 3: Build the A3 Field Study visual system and semantic hero + +**Files:** + +- Create: `site/src/styles/tokens.css` +- Create: `site/src/styles/field-study.css` +- Create: `site/src/components/BrandMark.astro` +- Create: `site/src/components/SiteHeader.astro` +- Create: `site/src/components/Hero.astro` +- Create: `site/src/components/StatusBadge.astro` +- Create: `site/src/layouts/LandingLayout.astro` +- Modify: `site/src/pages/index.astro` +- Modify: `site/src/pages/en/index.astro` +- Create: `site/tests/hero-structure.test.ts` + +**Token contract:** + +```css +:root { + --field-ink: #050608; + --field-ink-soft: #0a0d10; + --field-text: #eef2f0; + --field-muted: #99a39f; + --field-line: rgb(189 211 202 / 14%); + --field-mint: #b9f3d2; + --field-cyan: #7ed8dc; + --field-warm: #d8c3a2; + --space-unit: 0.25rem; + --motion-slow: 18s; + --motion-enter: 700ms; +} +``` + +- [ ] Write structure tests first for exactly one `h1`, semantic header/nav/main, + visible Developer Preview status, primary install and docs actions, no fake + metrics, correct localized labels, language alternates, and functional content + without Canvas. +- [ ] Define a restrained mineral palette, type hierarchy, spacing rhythm, + hairline treatment, focus ring, selection colors, and two motion durations in + tokens. Bundle or self-host fonts only when their licenses and subset sizes are + recorded; otherwise use a deliberate system stack. +- [ ] Compose an asymmetric hero: compact mark/header, high-impact AgenNet name, + one precise coordination statement, status boundary, two actions, and a quiet + protocol notation strip. Avoid centered SaaS-card composition. +- [ ] Build the CSS-only atmosphere with grid, grain via a tiny local texture or + layered gradients, radial light, and bottom black fade. The fallback must look + complete before WebGL is mounted. +- [ ] Add a restrained staged entrance for header, title, statement, and actions. + Disable all nonessential entrance motion under `prefers-reduced-motion`. +- [ ] Run unit tests, Astro check/build, keyboard navigation, and snapshots with + JavaScript disabled at 390×844 and 1440×1000. +- [ ] Commit: + +```text +[feat][Site][3/10] Compose Field Study hero + +Root cause: NA +Solution: Establish A3 visual tokens and an atmospheric hero that +remains complete without WebGL. +Risks: Typography may vary until approved fonts ship. +Dependency: Site step 2. +Links: docs/superpowers/specs/ +2026-08-14-node-bootstrap-and-pages-design.md +``` + +## Task 4: Implement deterministic WebGL2 light ribbons and point field + +**Files:** + +- Create: `site/src/components/field/FieldCanvas.astro` +- Create: `site/src/components/field/field-client.ts` +- Create: `site/src/components/field/renderer.ts` +- Create: `site/src/components/field/shaders.ts` +- Create: `site/src/components/field/capability.ts` +- Create: `site/tests/field-math.test.ts` +- Create: `site/e2e/field-canvas.spec.ts` +- Modify: `site/src/components/Hero.astro` +- Modify: `site/src/styles/field-study.css` + +**Renderer boundary:** + +```ts +export interface FieldRenderer { + resize(width: number, height: number, dpr: number): void; + setPointer(x: number, y: number, active: boolean): void; + render(timeSeconds: number): void; + dispose(): void; +} + +export type FieldQuality = 'full' | 'reduced' | 'static'; +``` + +- [ ] Write math tests for deterministic lattice coordinates, pointer force + radius/falloff, viewport normalization, DPR cap, quality selection, stable + seed, and zero NaN/Infinity values at boundary coordinates. +- [ ] Write browser tests that fail before implementation: WebGL canvas mounts + once, shaders compile, point field is nonempty, pointer changes a sampled + frame, animation pauses when hidden, context loss falls back, reduced motion + renders one frame, and no console/WebGL errors occur. +- [ ] Implement one full-screen WebGL2 canvas behind semantic content. Use a + deterministic ordered lattice in a point-sprite pass and displace only points + near the normalized pointer with smooth radial falloff and damped return. +- [ ] Implement two or three large slow ribbons as an analytic fragment shader, + using domain-warped signed distance bands, low-frequency motion, controlled + mint/cyan/warm energy, soft additive blending, and dark occlusion. Do not + allocate per-frame particles or simulate random physics. +- [ ] Cap DPR at 1.5 on full quality, reduce lattice density/steps on narrow or + low-core devices, and select static mode for reduced motion, missing WebGL2, + context loss, or shader compilation failure. +- [ ] Use one `requestAnimationFrame` loop, `ResizeObserver`, passive pointer + events, page visibility suspension, and complete listener/GPU cleanup. Never + read device identifiers or send timing data. +- [ ] Add a 60-second performance test at 1440×1000: no unbounded memory growth, + no long task over the documented threshold after warmup, and an average frame + budget appropriate to the test runner. Record the measured budget rather than + claiming a universal FPS. +- [ ] Run unit/E2E tests in Chromium, reduced-motion mode, software-rendered + fallback, and mobile viewport. Visually compare to the approved A3 study. +- [ ] Commit: + +```text +[feat][Site][4/10] Render interactive field + +Root cause: NA +Solution: Add deterministic WebGL2 ribbons and a pointer-responsive +ordered lattice with bounded quality and complete static fallback. +Risks: GPU drivers can render subtle shader differences across devices. +Dependency: Site step 3. +Links: plan/03-v1-public-site.md +``` + +## Task 5: Add verified installation paths and local copy controls + +**Files:** + +- Create: `site/src/components/InstallPanel.astro` +- Create: `site/src/components/InstallTabs.astro` +- Create: `site/src/components/CopyButton.astro` +- Create: `site/src/components/copy-client.ts` +- Create: `site/e2e/install-panel.spec.ts` +- Modify: `site/src/pages/index.astro` +- Modify: `site/src/pages/en/index.astro` + +- [ ] Write E2E tests for macOS/Linux tabs, Agent guide, direct CLI path, + fixed-version commands, correct base-path downloads, keyboard tabs, live-region + copy feedback, clipboard failure, no invitation input, and no network request + on copy. +- [ ] Present three entry paths without duplicating security logic: verified + human install, “send this guide to your Agent,” and direct non-Agent CLI. +- [ ] Separate convenience and high-assurance verification visually and + textually. Explain that installation does not enroll the node. +- [ ] Bind all version, manifest, checksum, and raw-guide URLs to typed generated + data. Build must fail when data is missing rather than displaying a moving + placeholder. +- [ ] Implement local clipboard behavior with explicit success/failure status, + selection fallback, and no analytics. Keep the full commands visible for + manual inspection. +- [ ] Run E2E tests in both locales, with clipboard denied, JavaScript disabled, + and narrow/mobile viewports. +- [ ] Commit: + +```text +[feat][Site][5/10] Add verified install paths + +Root cause: NA +Solution: Present human, Agent, and CLI paths from one +validated release source with accessible local copy controls. +Risks: High assurance requires the GitHub CLI attestation workflow. +Dependency: Site step 4 and installation plan step 7. +Links: docs/bootstrap/node-setup.md +``` + +## Task 6: Complete the landing-page narrative without invented proof + +**Files:** + +- Create: `site/src/components/ProtocolFlow.astro` +- Create: `site/src/components/Principles.astro` +- Create: `site/src/components/CurrentProof.astro` +- Create: `site/src/components/RoadmapBoundary.astro` +- Create: `site/src/components/SiteFooter.astro` +- Modify: `site/src/data/copy.ts` +- Modify: `site/src/pages/index.astro` +- Modify: `site/src/pages/en/index.astro` +- Create: `site/tests/public-claims.test.ts` + +- [ ] Write claim tests that allow only evidence-backed status phrases and fail + on Internet-scale, sandbox, quota, failover, public-network, self-improving, or + multi-host claims not marked verified by the canonical status data. +- [ ] Add a compact protocol flow showing Intent → routing → bilateral Contract + → Evidence → independent verification → Accepted. Use semantic HTML and CSS, + not a raster diagram. +- [ ] Add principles for signed objects, capability-scoped grants, independent + verification, and replaceable adapters. Distinguish candidate invariants from + revisable Developer Preview defaults. +- [ ] Add “currently proven” and “not yet proven” sections sourced from the + design verification matrix. Show exact release/commit only when generated + status evidence exists; never synthesize live counts. +- [ ] End with docs/GitHub calls to action and a compact footer. Preserve the + hero's visual hierarchy instead of turning each section into equal cards. +- [ ] Run claim tests, semantic heading checks, both locale builds, and manual + copy review against README/design. +- [ ] Commit: + +```text +[feat][Site][6/10] Complete honest narrative + +Root cause: NA +Solution: Explain the protocol, proof boundary, and roadmap through an +evidence-gated bilingual landing narrative. +Risks: Status copy must be regenerated after each milestone change. +Dependency: Site step 5. +Links: docs/design/agenet-v0.1.md +``` + +## Task 7: Build the bilingual Starlight documentation system + +**Files:** + +- Modify: `site/astro.config.mjs` +- Create: `site/src/content/docs/guide/*.mdx` +- Create: `site/src/content/docs/en/guide/*.mdx` +- Create: `site/src/content/docs/concepts/*.mdx` +- Create: `site/src/content/docs/en/concepts/*.mdx` +- Create: `site/src/content/docs/reference/*.mdx` +- Create: `site/src/content/docs/en/reference/*.mdx` +- Create: `site/src/content/docs/security/*.mdx` +- Create: `site/src/content/docs/en/security/*.mdx` +- Create: `site/src/content/docs/status/*.mdx` +- Create: `site/src/content/docs/en/status/*.mdx` +- Create: `site/tests/docs-parity.test.ts` + +**Required documentation map:** + +```text +guide: quickstart, create-a-domain, join-a-node, agent-bootstrap/v0.2, + non-agent-node, enable-an-adapter +concepts: identity-and-trust, capabilities, contracts-and-evidence +reference: cli, configuration, bootstrap-manifest +security: threat-model, revocation-and-recovery +status: validation-matrix; plus roadmap and changelog +``` + +- [ ] Write parity tests first: every required slug exists in both locales, + stable anchors match, command blocks match, internal links resolve under the + base path, and no page uses a rejected AgenNet spelling. +- [ ] Configure explicit Starlight sidebars, locale labels, GitHub edit links, + last-updated metadata, and custom CSS that shares the landing tokens without + reducing docs readability. +- [ ] Write concept pages from the approved design and current Rust interfaces. + Label implemented, planned, and deferred behavior on every boundary page. +- [ ] Generate CLI/error references from Clap and stable error definitions; + fail `check:generated` when checked-in reference output is stale. +- [ ] Add operational recovery paths for wrong network boundary, stale + revocation, expired credentials, unavailable Authority, failed service start, + and safe uninstall. +- [ ] Run parity/link/spelling tests, Starlight search build, and manually find + the same topic through Chinese and English navigation. +- [ ] Commit: + +```text +[doc][Site][7/10] Publish bilingual docs + +Root cause: NA +Solution: Add parallel Chinese and English concept and reference +documentation with generated CLI and error contracts. +Risks: Prose translation still needs native-language editorial review. +Dependency: Site step 6. +Links: plan/03-v1-public-site.md +``` + +## Task 8: Enforce accessibility, performance, and content integrity + +**Files:** + +- Create: `site/playwright.config.ts` +- Create: `site/e2e/accessibility.spec.ts` +- Create: `site/e2e/navigation.spec.ts` +- Create: `site/e2e/performance.spec.ts` +- Create: `site/scripts/check-public-output.mjs` +- Modify: `site/package.json` + +- [ ] Add Playwright checks for keyboard-only navigation, visible focus, + skip-link behavior, language switching, tab semantics, reduced motion, forced + colors, 200% zoom, and meaningful page landmarks. +- [ ] Test widths 320, 390, 768, 1024, 1440, and 1920; prevent horizontal + overflow, clipped actions, unreadable line lengths, and Canvas interception of + clicks or selection. +- [ ] Establish budgets for initial JS, CSS, fonts, images, WebGL code, and total + page weight. Fail the build when checked assets exceed the recorded budgets; + exclude no first-party file from measurement. +- [ ] Make `check-public-output.mjs` scan built HTML, JS, source maps, JSON, + Markdown, and headers for private paths, secret-shaped values, source-map + leakage, wrong base URLs, rejected spellings, broken canonical/hreflang links, + and external trackers. +- [ ] Run E2E with WebGL2, forced WebGL failure, JavaScript disabled, reduced + motion, mobile emulation, and both locales. Record real measured page weights + and interaction timings in a checked-in test note. +- [ ] Commit: + +```text +[chore][Site][8/10] Gate site quality + +Root cause: NA +Solution: Add accessibility, responsive, performance, fallback, and +output integrity gates for the complete static site. +Risks: Browser differences require reviewed visual tolerances. +Dependency: Site step 7. +Links: plan/03-v1-public-site.md +``` + +## Task 9: Add least-privilege GitHub Pages deployment + +**Files:** + +- Create: `.github/workflows/pages.yml` +- Create: `site/scripts/verify-deploy-artifact.mjs` +- Create: `site/tests/deploy-artifact.test.ts` +- Modify: `README.md` + +- [ ] Write deployment-artifact tests first: require `.nojekyll`, root and + English pages, docs/search assets, raw guides, release manifest, correct base + links, no source maps, no secrets, and a deterministic content inventory. +- [ ] Create a `master` push/manual workflow with a concurrency group that + cancels superseded builds. Pin checkout, pnpm setup, Node setup, Astro Pages + action, upload-pages-artifact, and deploy-pages Actions to full commit SHAs. +- [ ] Give the build job only `contents: read`; give the deploy job only + `pages: write` and `id-token: write`. Declare the GitHub Pages environment and + use its returned URL. +- [ ] Run clean `pnpm install --frozen-lockfile`, canonical-content check, type + check, unit tests, build, public-output scan, deployment-artifact validation, + and E2E against the built static server before upload. +- [ ] Deploy only the generated `site/dist` directory. Do not expose repository + roots, `.env`, `.local`, evidence journals, plans, or administrative docs as + downloadable site assets. +- [ ] Document repository Settings prerequisites and the expected URL + `https://nexa-language.github.io/AgenNet/`; do not make DNS or custom-domain + changes in this milestone. +- [ ] Validate the workflow with `actionlint` and a pull-request build that has no + Pages write permission. +- [ ] Commit: + +```text +[feat][Site][9/10] Deploy GitHub Pages + +Root cause: NA +Solution: Build, validate, and deploy only the static artifact through a +least-privilege master-only GitHub Pages workflow. +Risks: Pages settings require organization-level permission. +Dependency: Site step 8. +Links: .github/workflows/pages.yml +``` + +## Task 10: Publish and verify the real public site + +**Files:** + +- Create: `docs/testing/public-site-acceptance.md` +- Create: `site/e2e/production.spec.ts` +- Modify: `ROADMAP.md` +- Modify: `README.md` +- Modify: `docs/design/agenet-v0.1.md` + +- [ ] Write production tests for HTTP success, canonical URL, Chinese root, + English path, docs search, raw Agent guides, release manifest, fixed-version + archive links, GitHub link, no mixed content, no trackers, and no invitation + input. +- [ ] Merge only through the user's normal reviewed MR path after rebasing the + feature branch onto current `master`; do not merge directly during execution. +- [ ] Enable GitHub Pages with GitHub Actions as source if repository settings do + not already match. This is the only external configuration mutation in the + site plan and must target `Nexa-Language/AgenNet` exactly. +- [ ] Wait for the Pages workflow, open the real URL, run production Playwright + tests, and inspect desktop/mobile/reduced-motion screenshots plus Canvas + fallback in the browser. +- [ ] Verify GitHub's deployed environment references the expected commit and + that the public manifest/archive attestations resolve to the same release. +- [ ] Record the URL, commit, workflow run, page-weight results, supported + locales, physical-device verification status, and known visual/platform + limitations in ROADMAP. Do not claim completion of multi-host onboarding if + Bootstrap Task 14 is still pending. +- [ ] Commit any evidence-only documentation update through a new feature commit + and the same review path: + +```text +[milestone][Site][10/10] Verify public site + +Root cause: NA +Solution: Validate the bilingual site, bootstrap artifacts, fallbacks, +and release-link integrity at the public URL. +Risks: GitHub Pages availability remains outside AgenNet's control. +Dependency: Site step 9 and reviewed master deployment. +Links: docs/testing/public-site-acceptance.md +``` + +## Final Acceptance Commands + +```bash +pnpm --dir site install --frozen-lockfile +pnpm --dir site check:generated +pnpm --dir site astro check +pnpm --dir site test +pnpm --dir site build +pnpm --dir site test:e2e +node site/scripts/check-public-output.mjs site/dist +node site/scripts/verify-deploy-artifact.mjs site/dist +``` + +The site is incomplete if Canvas is required to understand or install AgenNet, +if release/guide content is manually duplicated, if reduced-motion or mobile +fallbacks are visually broken, if public output contains private state, or if +the deployed page implies capabilities that the protocol and physical-device +evidence have not established. diff --git a/plan/03-v2-public-sites.md b/plan/03-v2-public-sites.md new file mode 100644 index 0000000..31b59c9 --- /dev/null +++ b/plan/03-v2-public-sites.md @@ -0,0 +1,420 @@ +# AgenNet GPT Sites and Documentation Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use +> `superpowers:subagent-driven-development` (recommended) or +> `superpowers:executing-plans` to implement this plan task-by-task. Steps use +> checkbox (`- [ ]`) syntax for tracking. Use `sites:sites-building` for the +> implementation and `sites:sites-hosting` only after the exact build passes. + +**Goal:** Publish a high-end bilingual AgenNet landing and documentation site +whose installation commands and Agent guide are generated from the immutable +`v0.2.0-preview.1` release. + +**Architecture:** A Sites-compatible Vinext project renders semantic landing +and documentation routes. A deterministic sync step validates the canonical +release manifest and guides, then emits typed site data plus raw public +bootstrap files. A bounded WebGL2 field enhances a complete CSS atmosphere; +all content, installation, and documentation remain usable without WebGL or +motion. + +**Tech Stack:** OpenAI Sites starter, Vinext, React, TypeScript, Vite, +`@openai/sites-vite-plugin`, CSS, WebGL2, Vitest, Playwright, GPT Sites hosting. + +## Global Constraints + +- Use **AgenNet** in public display copy. +- Chinese is the default locale; every public document has an English mirror. +- Show `v0.2.0-preview.1` and **Developer Preview — physical acceptance + pending**; do not claim stable, physical, or Internet-scale validation. +- Commands, URLs, hashes, targets, and versions are generated from the release + manifest and canonical guides; components may not hardcode them. +- No account, invitation form, credential input, telemetry, live-node status, + private IP, local path, or raw evidence is hosted. +- WebGL2 is optional; semantic content and CSS fallback are complete. +- Respect reduced motion, keyboard access, touch, contrast, and responsive + layout. +- Use one Sites project and one stable preview/deployment tab. +- Commit messages use the five-section format. + +--- + +## Task 1: Initialize the Sites project and bilingual route shell + +**Files:** + +- Create: `site/` through the bundled Sites initializer +- Modify: `site/app/layout.tsx` +- Modify: `site/app/page.tsx` +- Create: `site/app/en/page.tsx` +- Create: `site/app/docs/[[...slug]]/page.tsx` +- Create: `site/app/en/docs/[[...slug]]/page.tsx` +- Create: `site/app/globals.css` +- Create: `site/components/SiteHeader.tsx` +- Create: `site/components/SiteFooter.tsx` +- Create: `site/content/navigation.ts` +- Create: `site/tests/routes.test.tsx` + +**Interfaces:** + +- Produces one semantic layout, locale-aware navigation, root routes, and docs + route shells consumed by later tasks. + +```ts +export type Locale = 'zh' | 'en'; + +export interface NavigationItem { + key: 'install' | 'docs' | 'github'; + href: string; + label: Record; +} +``` + +- [ ] **Step 1: Initialize once and inspect the minimal Sites files** + +Run the bundled `scripts/init-site.sh` with `site/` as the target. Preserve its +package manager, lockfile, Vinext structure, Vite Sites plugin, and +`.openai/hosting.json`. Do not initialize a second frontend. + +- [ ] **Step 2: Write failing semantic route tests** + +Require one `h1`, one `main`, header/nav/footer landmarks, correct locale links, +Developer Preview status, install/docs actions, no fake metrics, and no +starter `codex-preview` metadata. + +```tsx +it('shows the honest preview status in both locales', async () => { + expect(renderRoute('/').getByText('物理设备验收待完成')).toBeVisible(); + expect(renderRoute('/en').getByText('Physical acceptance pending')).toBeVisible(); +}); +``` + +- [ ] **Step 3: Run RED** + +Run the site test command selected by the starter. Expected: route modules and +product copy are missing. + +- [ ] **Step 4: Implement the smallest recognizable first slice** + +Replace `SkeletonPreview` with a semantic AgenNet header, exact product name, +one coordination statement, status badge, and install/docs actions. Add a +complete dark CSS fallback but no WebGL implementation yet. Remove starter +metadata and unused skeleton imports. + +- [ ] **Step 5: Start and hand off the first meaningful preview** + +Keep `npm run dev` alive, make one lightweight request to the exact local URL, +and open the compiled product slice in Codex with one stable tab ID. Do not +perform browser QA or additional planned source edits before this handoff. + +- [ ] **Step 6: Complete route shells and verify GREEN** + +Add locale-aware header/footer and docs shells, then run unit tests and the +production build. + +- [ ] **Step 7: Commit** + +```text +[feat][Site][1/5] Establish Sites shell + +Root cause: NA +Solution: Add one semantic bilingual Vinext shell for AgenNet. +Risks: Content routes remain incomplete until generated data lands. +Dependency: Preview release plan task 1. +Links: plan/03-v2-public-sites.md +``` + +## Task 2: Synchronize immutable release and documentation data + +**Files:** + +- Create: `site/scripts/sync-public-data.ts` +- Create: `site/lib/release-data.ts` +- Create: `site/generated/release.ts` +- Create: `site/generated/docs.ts` +- Create: `site/public/bootstrap/v0.2/manifest.json` +- Create: `site/public/bootstrap/v0.2/agent-bootstrap.md` +- Create: `site/public/bootstrap/v0.2/agent-bootstrap.en.md` +- Create: `site/tests/generated-data.test.ts` +- Modify: `site/package.json` + +**Interfaces:** + +- Consumes: the validated canonical release manifest and guides from + `plan/02-v2-installation-surfaces.md`. +- Produces: + +```ts +export interface PublicReleaseData { + project: 'AgenNet'; + version: '0.2.0-preview.1'; + status: 'developer-preview-physical-pending'; + installerUrl: string; + artifacts: ReadonlyArray<{ + target: 'macos-arm64' | 'macos-x86_64' | 'linux-arm64' | 'linux-x86_64'; + url: string; + sha256: string; + sizeBytes: number; + }>; +} +``` + +- [ ] **Step 1: Write failing generated-data tests** + +Require byte-for-byte public manifest parity, fixed installer URL, four targets, +strict preview status, guide command parity, normalized line endings, and no +moving URL, local path, private IP, secret-shaped token, raw evidence, or +native-Windows claim. + +- [ ] **Step 2: Run RED** + +Run `npm test -- generated-data` and confirm generated files are missing. + +- [ ] **Step 3: Implement strict synchronization** + +Parse and validate the canonical manifest before writing. Copy only approved +public fields. Generate TypeScript and raw static files atomically, sort data, +normalize line endings, and refuse unpublished or version-mismatched input. +Add `prebuild` and `check:generated`; running sync twice must produce no diff. + +- [ ] **Step 4: Verify GREEN** + +Run sync twice, generated tests, `check:generated`, the site build, and scans +for paths/IPs/secrets/moving URLs. + +- [ ] **Step 5: Commit** + +```text +[feat][Site][2/5] Sync preview release data + +Root cause: NA +Solution: Generate all public commands and bootstrap files from release truth. +Risks: Site builds intentionally stop when the release is incomplete. +Dependency: Release manifest and canonical guides. +Links: site/public/bootstrap/v0.2/manifest.json +``` + +## Task 3: Build the Field Study hero and bounded particle renderer + +**Files:** + +- Create: `site/components/Hero.tsx` +- Create: `site/components/StatusBadge.tsx` +- Create: `site/components/field/FieldCanvas.tsx` +- Create: `site/components/field/renderer.ts` +- Create: `site/components/field/shaders.ts` +- Create: `site/components/field/capability.ts` +- Create: `site/styles/tokens.css` +- Create: `site/styles/field-study.css` +- Create: `site/tests/field-math.test.ts` +- Create: `site/e2e/field-canvas.spec.ts` +- Modify: `site/app/page.tsx` +- Modify: `site/app/en/page.tsx` + +**Interfaces:** + +```ts +export type FieldQuality = 'full' | 'reduced' | 'static'; + +export interface FieldRenderer { + resize(width: number, height: number, dpr: number): void; + setPointer(x: number, y: number, active: boolean): void; + render(timeSeconds: number): void; + dispose(): void; +} +``` + +- [ ] **Step 1: Write deterministic math and browser RED tests** + +Test stable lattice coordinates, pointer normalization/falloff, DPR cap 1.5, +quality selection, finite boundary values, one canvas mount, shader compilation, +nonempty point field, pointer frame change, visibility pause, context-loss +fallback, reduced-motion single frame, teardown, and zero console/WebGL errors. + +- [ ] **Step 2: Run RED** + +Run focused unit and Playwright tests. Expected: renderer modules are missing. + +- [ ] **Step 3: Implement visual tokens and complete fallback** + +Define the dark mineral palette, precise typography, spacing, hairlines, focus +ring, selection, staged entrance, grid/grain/radial light, and bottom fade. Use +CSS layers rather than a generated SVG. Disable nonessential motion under +`prefers-reduced-motion`. + +- [ ] **Step 4: Implement one bounded WebGL2 renderer** + +Render an ordered point lattice and two or three analytic slow ribbons. Use one +RAF loop, a stable seed, passive pointer events, smooth local displacement, +visibility suspension, `ResizeObserver`, full cleanup, static/reduced modes, +and no telemetry or device fingerprinting. Never allocate per-frame particles. + +- [ ] **Step 5: Verify GREEN and sustained behavior** + +Run unit/E2E tests at desktop/mobile/reduced-motion/no-WebGL modes. Run a +60-second renderer test and assert bounded memory, no accumulating listeners, +and no non-finite shader input; report the measured runner budget without a +universal FPS claim. + +- [ ] **Step 6: Commit** + +```text +[feat][Site][3/5] Render the AgenNet field + +Root cause: NA +Solution: Add bounded light ribbons and an ordered responsive point field. +Risks: GPU output varies subtly while the CSS fallback stays authoritative. +Dependency: Sites shell task 1. +Links: docs/superpowers/specs/ +2026-08-15-preview-release-skill-sites-design.md +``` + +## Task 4: Build bilingual installation and usage documentation + +**Files:** + +- Create: `site/content/docs.ts` +- Create: `site/components/docs/DocsLayout.tsx` +- Create: `site/components/docs/DocsSidebar.tsx` +- Create: `site/components/docs/InstallPanel.tsx` +- Create: `site/components/docs/CopyButton.tsx` +- Create: `site/components/docs/Callout.tsx` +- Create: `site/components/docs/CodeBlock.tsx` +- Create: `site/tests/docs-content.test.tsx` +- Create: `site/e2e/docs.spec.ts` +- Modify: bilingual docs route modules + +**Interfaces:** + +```ts +export type DocSlug = + | 'index' + | 'install' + | 'create-domain' + | 'join-node' + | 'agent-setup' + | 'run-pursuit' + | 'lifecycle' + | 'security' + | 'limitations'; + +export interface DocRecord { + slug: DocSlug; + locale: Locale; + title: string; + description: string; + sections: readonly DocSection[]; +} + +export interface DocSection { + id: string; + heading: string; + blocks: readonly DocBlock[]; +} + +export type DocBlock = + | { kind: 'paragraph'; text: string } + | { kind: 'command'; commandKey: string } + | { kind: 'callout'; tone: 'info' | 'warning'; text: string }; +``` + +- [ ] **Step 1: Write content and interaction RED tests** + +Require every slug in both locales, exact command parity, correct language +alternates and detail metadata, keyboard sidebar, touch layout, copy success +and failure feedback, fixed installer selection for macOS/Linux/WSL2, secret +warnings beside enrollment, and limitations/status on every installation path. + +- [ ] **Step 2: Run RED** + +Run focused docs tests and confirm records/components are missing. + +- [ ] **Step 3: Implement typed docs from canonical data** + +Build concise guides for installation, Domain creation, node join, the Agent +sentence and Skill, pursuit, lifecycle, security, and limitations. Commands +come from generated data or canonical guide records. Documentation never asks +for an Invitation in a browser or chat. + +- [ ] **Step 4: Implement accessible navigation and local copy controls** + +Add responsive docs navigation, anchored headings, skip link, visible focus, +copy buttons with live-region feedback, locale switch preserving the slug, and +install tabs. Use no analytics, persistence, or remote code execution. + +- [ ] **Step 5: Verify GREEN** + +Run unit/E2E tests, keyboard checks, 390×844 and 1440×1000 layouts, no-JS +content checks, localized detail metadata validation, and the production build. + +- [ ] **Step 6: Commit** + +```text +[feat][Site][4/5] Publish bilingual usage docs + +Root cause: NA +Solution: Add typed install, bootstrap, lifecycle, and security documentation. +Risks: New CLI commands intentionally require regenerated content. +Dependency: Generated public data task 2. +Links: plan/03-v2-public-sites.md +``` + +## Task 5: Seal and package the Sites release + +**Files:** + +- Create: `site/public/og.png` +- Modify: `site/app/layout.tsx` +- Modify: `site/.openai/hosting.json` +- Create: `site/tests/publication.test.ts` +- Create: `site/scripts/preflight-site.ts` +- Modify: `README.md` +- Modify: `ROADMAP.md` + +**Interfaces:** + +- Consumes: Tasks 1–4 and the published preview manifest. +- Produces: the exact validated Sites source and package consumed by + `sites:sites-hosting`. + +- [ ] **Step 1: Write the failing publication preflight** + +Require all routes, raw bootstrap files, generated-data cleanliness, site +metadata, public preview status, accessibility landmarks, reduced-motion +fallback, zero starter artifacts, no secrets/private paths/IPs/evidence, and a +valid `.openai/hosting.json` containing only the Sites project ID. + +- [ ] **Step 2: Run RED** + +Run publication tests and record missing social metadata and hosting state. + +- [ ] **Step 3: Generate one cohesive social preview card** + +After headline, palette, typography, and motifs are frozen, make exactly one +`imagegen` request for a landscape AgenNet social card containing the correct +project name and preview positioning. Inspect text; retry once only if +unusable. Save the accepted card as `public/og.png`; otherwise omit the image. + +- [ ] **Step 4: Complete metadata and Sites packaging** + +Use host-derived absolute URLs for root Open Graph/X metadata. Detail docs use +record-specific titles/descriptions and explicitly clear inherited images. +Create or reuse the Sites project, persist only `project_id`, build once, and +package with the Sites plugin helper. + +- [ ] **Step 5: Run final GREEN gates** + +Run tests, `npm run build`, generated checks, Playwright scenarios, publication +preflight, archive validation, and scans. Keep the preview server alive until +hosting finishes. + +- [ ] **Step 6: Commit** + +```text +[chore][Site][5/5] Seal GPT Sites release + +Root cause: NA +Solution: Validate and package the exact bilingual preview site for hosting. +Risks: Public deployment still requires an explicit Sites access decision. +Dependency: Site tasks 1-4 and published preview manifest. +Links: plan/04-v1-preview-release-orchestration.md +``` diff --git a/plan/04-v1-preview-release-orchestration.md b/plan/04-v1-preview-release-orchestration.md new file mode 100644 index 0000000..7295f41 --- /dev/null +++ b/plan/04-v1-preview-release-orchestration.md @@ -0,0 +1,290 @@ +# AgenNet Preview Release and Sites Publication Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use +> `superpowers:subagent-driven-development` (recommended) or +> `superpowers:executing-plans` to implement this plan task-by-task. Steps use +> checkbox (`- [ ]`) syntax for tracking. Use `github:yeet` for source branch +> publication and `sites:sites-hosting` for the public documentation site. + +**Goal:** Publish `v0.2.0-preview.1`, deploy the bilingual GPT Sites experience, +and prove the public one-sentence bootstrap path from a fresh Agent inside +WSL2 without claiming the later physical AgenNet acceptance milestone. + +**Architecture:** The reviewed release candidate is tagged once and GitHub +Actions publishes immutable artifacts. The validated Sites source consumes the +published manifest and is deployed publicly through GPT Sites. A fresh WSL2 +Agent then follows the public raw guide and Skill; only redacted public status +is retained as installation-surface evidence. + +**Tech Stack:** Git, GitHub Releases and Actions, GPT Sites hosting, WSL2, +AgenNet CLI and Skill. + +## Global Constraints + +- Publish only from a clean reviewed commit on `feat/release-skill-sites`. +- Tag exactly `v0.2.0-preview.1`; never move or recreate the tag. +- Do not publish when any archive, checksum, attestation, guide, Skill, or site + build is missing or inconsistent. +- Site access is public because the user requested documentation for everyone; + do not publish invitations, private endpoints, credentials, or evidence. +- WSL2 Agent testing never sends an Invitation, passphrase, key, token, private + IP, CIDR, or raw diagnostic log into chat. +- Passing WSL installation proves the public installation surface only. The + existing two-physical-device Task 14 remains pending. +- Every failure preserves the last known good release/site and records a + versioned correction rather than mutating published assets. + +--- + +## Task 1: Publish the GitHub prerelease + +**Files:** + +- Consume: release candidate and preflight artifacts from + `plan/02-v2-installation-surfaces.md` +- Modify: `ROADMAP.md` only after publication succeeds +- Create: `.superpowers/sdd/02-v1-release-skill-sites/release-report.md` + as ignored local evidence + +**Interfaces:** + +- Produces immutable public URLs under: + +```text +https://github.com/Nexa-Language/AgenNet/releases/tag/v0.2.0-preview.1 +https://github.com/Nexa-Language/AgenNet/releases/download/ +v0.2.0-preview.1/{manifest-approved-asset-name} +``` + +- [ ] **Step 1: Verify the exact release candidate** + +Run default/all-feature Rust gates, archive/installer/guide/Skill preflights, +`git diff --check`, secret scans, `git fsck`, and confirm local/remote branch +HEAD equality. Record the exact full commit SHA. + +- [ ] **Step 2: Push the reviewed source branch** + +Use SSH `git push -u origin feat/release-skill-sites`. Do not use force push or +alter Git configuration. Confirm the remote branch resolves to the exact local +SHA. + +- [ ] **Step 3: Create and push one annotated tag** + +First assert that neither local nor remote already contains the tag. Then run: + +```bash +git tag -a v0.2.0-preview.1 \ + -m "AgenNet v0.2.0-preview.1 Developer Preview" +git push origin refs/tags/v0.2.0-preview.1 +``` + +If either command reports an existing divergent tag, stop; never delete or +move it automatically. + +- [ ] **Step 4: Wait for the tag workflow and inspect every asset** + +Require all four native archives, `release-manifest-v1.json`, `SHA256SUMS`, +`install.sh`, both raw Agent guides, the Skill package, release notes, and four +attestations. Download to a temporary directory, rerun offline verification, +and compare manifest commit/version with the tag. + +- [ ] **Step 5: Record the release result** + +Update ROADMAP only after the public prerelease and downloads pass. The report +contains public asset names, sizes, hashes, workflow conclusion, and status; +it contains no credential, IP, local path, or source credential. + +- [ ] **Step 6: Commit the publication record** + +```text +[chore][Release] Record preview publication + +Root cause: NA +Solution: Record the verified immutable v0.2.0-preview.1 prerelease. +Risks: Physical two-device acceptance remains pending. +Dependency: GitHub tag v0.2.0-preview.1. +Links: GitHub release v0.2.0-preview.1. +``` + +## Task 2: Publish the GPT Sites landing and documentation + +**Files:** + +- Consume: exact validated source from `plan/03-v2-public-sites.md` +- Modify: `site/.openai/hosting.json` with `project_id` only +- Modify: `ROADMAP.md` after deployment succeeds +- Create: ignored deployment report under + `.superpowers/sdd/02-v1-release-skill-sites/` + +**Interfaces:** + +- Produces one public Sites URL and stable raw bootstrap URLs: + +```rust +pub struct PublishedSiteV1 { + pub base_url: String, + pub agent_guide_zh_url: String, + pub agent_guide_en_url: String, + pub manifest_url: String, + pub release_version: String, +} +``` + +- [ ] **Step 1: Synchronize from the published manifest and rebuild once** + +Fetch the public fixed manifest, validate it, regenerate site data, require a +clean generated diff, and run the exact production build plus publication +preflight. Do not substitute local draft manifest data. + +- [ ] **Step 2: Create or reuse the Sites project** + +Call `create_site` once for a new project, persist only `project_id` in +`.openai/hosting.json`, and retain the returned source credential in memory. +If quota or permission fails, stop without changing the slug or access level. + +- [ ] **Step 3: Commit and push exact validated site source** + +Commit the hosting metadata with the five-section format. Push through the +temporary per-command HTTP authorization header returned by Sites; never store +the credential in a remote URL, Git config, file, log, or final response. Use +the pushed branch-head SHA as the Sites `commit_sha`. + +- [ ] **Step 4: Package, save, and publicly deploy one version** + +Use the Sites plugin `scripts/package-site.sh`, inspect required worker/static +outputs, save one version, and deploy publicly. The user has explicitly asked +for a public teaching site; if the connector still presents an access-level +approval gate, request that exact public approval before deployment. + +- [ ] **Step 5: Poll to a terminal result and open the deployed URL** + +Poll `get_deployment_status` until succeeded or failed. On success, reuse the +single preview browser tab and navigate it to the deployed URL. Verify the +landing, install page, both raw Agent guide URLs, public manifest, and one +Chinese plus one English detail route using bounded HTTP checks. + +- [ ] **Step 6: Record deployment without implementation details** + +Update ROADMAP with the public URL, visible status, route set, and release +version. Keep source credentials, project internals, archives, and temporary +paths out of user-facing output. + +## Task 3: Run the fresh WSL2 Agent one-sentence acceptance + +**Files:** + +- Create: `docs/testing/wsl-agent-install-acceptance.md` +- Create: `schemas/wsl-agent-install-evidence-v1.schema.json` +- Create: `scripts/verify-wsl-agent-install-evidence.sh` +- Create: ignored redacted evidence under `.local/evidence/` +- Modify: `ROADMAP.md` after a passing run + +**Interfaces:** + +- Consumes: public Sites Agent guide URL, Skill package, GitHub prerelease, and + a fresh Agent process running inside WSL2. +- Produces redacted installation-surface evidence: + +```rust +pub struct WslAgentInstallEvidenceV1 { + pub schema_version: u32, // exactly 1 + pub result: String, // exactly "pass" + pub environment: String, // exactly "wsl2" + pub release: String, // exactly "0.2.0-preview.1" + pub git_commit: String, // equals the Task 1 published commit + pub binary_verified: bool, + pub skill_loaded: bool, + pub systemd_user_ready: bool, + pub secret_boundary_respected: bool, + pub node_join_completed_locally: bool, + pub service_running: bool, + pub doctor_public_status: String, // exactly "healthy" +} +``` + +The final schema substitutes the exact public commit at generation time and +rejects unknown fields; it never stores Node ID, IP, invitation, paths, logs, +tokens, credentials, or hashes of secrets. + +- [ ] **Step 1: Write the evidence schema and failing verifier cases** + +Reject wrong environment/version/commit, native-Windows claims, missing Skill, +unverified binary, no systemd, skipped secret boundary, failed join, stopped +service, unhealthy doctor, unknown fields, private IP/path patterns, and +secret-shaped strings. + +- [ ] **Step 2: Start a genuinely fresh Agent inside WSL2** + +The Agent must not receive repository files, local unpublished guides, expected +command output, or a preinstalled AgenNet Skill. Construct the sentence from +the validated `PublishedSiteV1.agent_guide_zh_url` returned by Task 2, and pass +the resulting literal URL to the Agent: + +```text +阅读 AgenNet 官方节点安装指南「Task 2 已验证的中文指南 URL」,把这台 +WSL2 机器配置成 Provider 节点;所有 Invitation 和密码只让我在本机 +TTY 输入。 +``` + +- [ ] **Step 3: Observe the installation and secret handoff** + +Confirm the Agent selects the fixed release, verifies it, installs/loads the +Skill, checks WSL2/systemd/overlay public readiness, and stops before enrollment +with the exact local TTY instruction. The human performs `agenet node join` +without pasting secrets or raw output back to the Agent. + +- [ ] **Step 4: Resume from public state and verify service health** + +The Agent runs only public status/doctor commands, starts the user service, +and reports stable public fields. Independently confirm the binary +version/commit, service manager state, and doctor result on the WSL machine. + +- [ ] **Step 5: Generate and validate redacted evidence** + +Manually construct the allowlisted evidence object, run the offline verifier, +and scan the complete transfer surface for invitations, keys, tokens, private +addresses, local paths, prompts, and raw logs. Any match makes the run fail. + +- [ ] **Step 6: Record the WSL installation result honestly** + +If passing, mark the one-sentence installation surface verified on WSL2. Do not +mark physical AgenNet multi-host acceptance complete; continue the existing +two-device runbook as the next milestone. + +## Task 4: Hand off to physical AgenNet acceptance + +**Files:** + +- Modify: `docs/testing/two-device-acceptance.md` only if public release commands + replace repository-local setup +- Modify: `ROADMAP.md` +- Modify: `README.md` + +**Interfaces:** + +- Consumes: published release/site and passing WSL installation evidence. +- Produces an updated physical runbook; no milestone commit until that run + independently passes. + +- [ ] **Step 1: Replace development-only setup with the fixed release path** + +Both physical devices install `v0.2.0-preview.1` through the public verified +installer and confirm the same release commit. Preserve all existing TTY, +private-overlay, mTLS, model-env, Contract, revocation, and evidence gates. + +- [ ] **Step 2: Re-run runbook/schema consistency checks** + +Require exact public URLs and version while rejecting private data and any +claim that WSL installation alone proves multi-host behavior. + +- [ ] **Step 3: Commit only the handoff documentation** + +```text +[doc] Use the public preview in physical tests + +Root cause: NA +Solution: Make physical acceptance consume the same published installer. +Risks: The physical multi-host result remains pending. +Dependency: Preview release, Sites, and WSL installation acceptance. +Links: docs/testing/two-device-acceptance.md +``` diff --git a/schemas/release-manifest-v1.schema.json b/schemas/release-manifest-v1.schema.json new file mode 100644 index 0000000..195817f --- /dev/null +++ b/schemas/release-manifest-v1.schema.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://agenet.dev/schemas/release-manifest-v1.schema.json", + "title": "AgenNet Release Manifest v1", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "project", + "version", + "git_commit", + "published_at", + "artifacts" + ], + "properties": { + "schema_version": { "const": 1 }, + "project": { "const": "AgenNet" }, + "version": { "const": "0.2.0-preview.4" }, + "git_commit": { "type": "string", "pattern": "^[0-9a-f]{40}$" }, + "published_at": { + "type": "string", + "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$" + }, + "artifacts": { + "type": "object", + "additionalProperties": false, + "required": [ + "macos-arm64", + "macos-x86_64", + "linux-arm64", + "linux-x86_64" + ], + "properties": { + "macos-arm64": { "$ref": "#/$defs/artifact" }, + "macos-x86_64": { "$ref": "#/$defs/artifact" }, + "linux-arm64": { "$ref": "#/$defs/artifact" }, + "linux-x86_64": { "$ref": "#/$defs/artifact" } + } + } + }, + "$defs": { + "artifact": { + "type": "object", + "additionalProperties": false, + "required": [ + "file_name", + "download_url", + "sha256", + "size_bytes", + "attestation_subject" + ], + "properties": { + "file_name": { "type": "string", "minLength": 1, "maxLength": 180 }, + "download_url": { "type": "string", "format": "uri" }, + "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "size_bytes": { "type": "integer", "minimum": 1, "maximum": 536870912 }, + "attestation_subject": { "type": "string", "minLength": 1, "maxLength": 180 } + } + } + } +} diff --git a/scripts/check-release-archive.sh b/scripts/check-release-archive.sh new file mode 100755 index 0000000..bf7d287 --- /dev/null +++ b/scripts/check-release-archive.sh @@ -0,0 +1,165 @@ +#!/bin/sh +set -eu + +repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) + +usage() { + printf '%s\n' \ + 'usage: scripts/check-release-archive.sh --archive PATH --target RUST_TRIPLE' \ + ' --version VERSION --commit FULL_SHA [--skip-binary-smoke]' +} + +archive= +target= +version= +commit= +skip_binary_smoke=0 + +while [ "$#" -gt 0 ]; do + case "$1" in + --archive) archive=${2-}; shift 2 ;; + --target) target=${2-}; shift 2 ;; + --version) version=${2-}; shift 2 ;; + --commit) commit=${2-}; shift 2 ;; + --skip-binary-smoke) skip_binary_smoke=1; shift ;; + --help|-h) usage; exit 0 ;; + *) usage >&2; exit 64 ;; + esac +done + +if [ -z "$archive" ] || [ -z "$target" ] || [ -z "$version" ] || [ -z "$commit" ]; then + usage >&2 + exit 64 +fi +if [ ! -f "$archive" ] || [ -L "$archive" ]; then + printf '%s\n' 'InvalidReleaseArchive' >&2 + exit 66 +fi + +python3 - "$archive" "$target" "$version" "$commit" "$repo_root" \ + "$skip_binary_smoke" <<'PY' +import gzip +import json +import os +import pathlib +import stat +import subprocess +import sys +import tarfile +import tempfile + +archive_path = pathlib.Path(sys.argv[1]) +target = sys.argv[2] +version = sys.argv[3] +commit = sys.argv[4] +repository = pathlib.Path(sys.argv[5]) +skip_binary_smoke = sys.argv[6] == "1" +root = f"agenet-v{version}-{target}" +expected = [ + (root, tarfile.DIRTYPE, 0o755), + (f"{root}/agenet", tarfile.REGTYPE, 0o755), + (f"{root}/LICENSE", tarfile.REGTYPE, 0o644), + (f"{root}/README.md", tarfile.REGTYPE, 0o644), + (f"{root}/RELEASE-METADATA.json", tarfile.REGTYPE, 0o644), +] + +if version != "0.2.0-preview.4": + raise SystemExit("UnsupportedReleaseVersion") +if target not in { + "aarch64-apple-darwin", + "x86_64-apple-darwin", + "aarch64-unknown-linux-gnu", + "x86_64-unknown-linux-gnu", +}: + raise SystemExit("UnsupportedReleaseTarget") +if len(commit) != 40 or any(character not in "0123456789abcdef" for character in commit): + raise SystemExit("InvalidReleaseCommit") +if archive_path.stat().st_size <= 0 or archive_path.stat().st_size > 512 * 1024 * 1024: + raise SystemExit("InvalidReleaseArchiveSize") + +header = archive_path.read_bytes()[:10] +if len(header) != 10 or header[:3] != b"\x1f\x8b\x08" or header[3] != 0: + raise SystemExit("InvalidDeterministicGzipHeader") +if int.from_bytes(header[4:8], "little") != 0: + raise SystemExit("InvalidDeterministicGzipTimestamp") + +try: + opened = tarfile.open(archive_path, mode="r:gz") +except (tarfile.TarError, OSError) as error: + raise SystemExit("InvalidReleaseArchive") from error + +with opened as release: + members = release.getmembers() + if len(members) != len(expected): + raise SystemExit("InvalidReleaseMemberSet") + bodies = {} + for member, (name, member_type, mode) in zip(members, expected): + if member.name != name or member.type != member_type: + raise SystemExit("InvalidReleaseMemberSet") + if member.mode != mode or member.uid != 0 or member.gid != 0: + raise SystemExit("InvalidReleaseMemberMetadata") + if member.uname != "root" or member.gname != "root" or member.mtime != 0: + raise SystemExit("InvalidReleaseMemberMetadata") + if member.linkname or member.pax_headers: + raise SystemExit("InvalidReleaseMemberMetadata") + if member.isfile(): + extracted = release.extractfile(member) + if extracted is None: + raise SystemExit("InvalidReleaseMember") + bodies[member.name] = extracted.read() + +metadata_name = f"{root}/RELEASE-METADATA.json" +try: + metadata = json.loads( + bodies[metadata_name], + object_pairs_hook=lambda pairs: ( + dict(pairs) + if len({key for key, _value in pairs}) == len(pairs) + else (_ for _ in ()).throw(ValueError("duplicate key")) + ), + ) +except (KeyError, UnicodeDecodeError, json.JSONDecodeError, ValueError) as error: + raise SystemExit("InvalidReleaseMetadata") from error +expected_metadata = { + "format": "agenet.release-metadata.v1", + "git_commit": commit, + "target": target, + "version": version, +} +canonical_metadata = ( + json.dumps(expected_metadata, sort_keys=True, separators=(",", ":")) + "\n" +).encode() +if metadata != expected_metadata or bodies[metadata_name] != canonical_metadata: + raise SystemExit("InvalidReleaseMetadata") + +for name in ("LICENSE", "README.md"): + source = repository / name + if not source.is_file() or bodies[f"{root}/{name}"] != source.read_bytes(): + raise SystemExit("ReleaseSourceMismatch") + +binary = bodies[f"{root}/agenet"] +if not binary: + raise SystemExit("InvalidReleaseBinary") +if skip_binary_smoke: + raise SystemExit(0) +with tempfile.TemporaryDirectory(prefix="agenet-archive-check.") as directory: + path = pathlib.Path(directory) / "agenet" + path.write_bytes(binary) + path.chmod(stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) + try: + result = subprocess.run( + [str(path), "--version"], + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + timeout=10, + env={"PATH": os.environ.get("PATH", "")}, + ) + except (OSError, subprocess.TimeoutExpired) as error: + raise SystemExit("ReleaseBinarySmokeFailed") from error + if result.returncode != 0 or result.stdout != f"agenet {version}\n".encode() or result.stderr: + raise SystemExit("ReleaseBinaryVersionMismatch") +PY + +printf '%s\n' 'release archive verified' diff --git a/scripts/install.sh.template b/scripts/install.sh.template new file mode 100644 index 0000000..e8d427d --- /dev/null +++ b/scripts/install.sh.template @@ -0,0 +1,146 @@ +#!/bin/sh +# shellcheck disable=SC2317,SC2329 +set -eu +umask 077 + +AGENET_RELEASE_VERSION='@@VERSION@@' +release_target= +archive_url= +archive_sha256= +archive_size= + +fail() { + printf '%s\n' "$1" >&2 + exit "${2:-1}" +} + +print_next() { + printf '%s\n' 'Next: agenet node doctor --output json' + case ":${PATH:-}:" in + *":$HOME/.local/bin:"*) ;; + *) printf '%s\n' 'Add ~/.local/bin to PATH before running agenet.' ;; + esac +} + +if [ "$#" -ne 0 ]; then + fail 'InstallerAcceptsNoArguments' 64 +fi +if [ -z "${HOME:-}" ]; then + fail 'HomeDirectoryUnavailable' 66 +fi +case "$HOME" in + /*) ;; + *) fail 'HomeDirectoryUnavailable' 66 ;; +esac + +system=$(uname -s 2>/dev/null || true) +machine=$(uname -m 2>/dev/null || true) +case "$system" in + MINGW*|MSYS*|CYGWIN*|Windows*) fail 'UnsupportedNativeWindowsUseWSL2' 65 ;; +esac +case "$system:$machine" in +# @@TARGET_CASES@@ + *) fail 'UnsupportedReleaseTarget' 65 ;; +esac + +if ! command -v curl >/dev/null 2>&1 || ! command -v tar >/dev/null 2>&1; then + fail 'InstallerDependencyUnavailable' 69 +fi + +private_dir=$(mktemp -d "${TMPDIR:-/tmp}/agenet-install.XXXXXX") || \ + fail 'InstallerTemporaryDirectoryUnavailable' 73 +pending_destination= +cleanup() { + if [ -n "$pending_destination" ]; then + rm -f -- "$pending_destination" + fi + rm -rf -- "$private_dir" +} +trap cleanup EXIT HUP INT TERM + +archive="$private_dir/archive.tar.gz" +effective_url=$(curl --fail --silent --show-error --location \ + --proto '=https' --proto-redir '=https' --max-redirs 5 \ + --connect-timeout 10 --max-time 120 --max-filesize "$archive_size" \ + --output "$archive" --write-out '%{url_effective}' "$archive_url") || \ + fail 'ReleaseDownloadFailed' 69 +case "$effective_url" in + https://github.com/Nexa-Language/AgenNet/releases/download/*|https://release-assets.githubusercontent.com/*) ;; + *) fail 'UnapprovedReleaseRedirect' 65 ;; +esac + +downloaded_size=$(wc -c <"$archive" | tr -d '[:space:]') +if [ "$downloaded_size" != "$archive_size" ]; then + fail 'ReleaseArchiveSizeMismatch' 65 +fi +if command -v sha256sum >/dev/null 2>&1; then + actual_sha256=$(sha256sum "$archive" | awk '{print $1}') +elif command -v shasum >/dev/null 2>&1; then + actual_sha256=$(shasum -a 256 "$archive" | awk '{print $1}') +else + fail 'ChecksumToolUnavailable' 69 +fi +if [ "$actual_sha256" != "$archive_sha256" ]; then + fail 'ReleaseChecksumMismatch' 65 +fi + +root="agenet-v${AGENET_RELEASE_VERSION}-${release_target}" +tar -tzf "$archive" >"$private_dir/names" || fail 'InvalidReleaseArchive' 65 +cat >"$private_dir/expected-names" <"$private_dir/layout" || fail 'InvalidReleaseArchive' 65 +cat >"$private_dir/expected-layout" <"$staged_binary" || \ + fail 'InvalidReleaseArchive' 65 +chmod 0700 "$staged_binary" +reported_version=$($staged_binary --version 2>/dev/null || true) +if [ "$reported_version" != "agenet $AGENET_RELEASE_VERSION" ]; then + fail 'ReleaseBinaryVersionMismatch' 65 +fi + +local_dir="$HOME/.local" +bin_dir="$local_dir/bin" +if [ -L "$local_dir" ] || [ -L "$bin_dir" ]; then + fail 'UnsafeInstallDirectory' 73 +fi +mkdir -p -- "$bin_dir" || fail 'InstallDirectoryUnavailable' 73 +destination="$bin_dir/agenet" +if [ -e "$destination" ] || [ -L "$destination" ]; then + if [ ! -f "$destination" ] || [ -L "$destination" ]; then + fail 'UnsafeExistingAgenNetPath' 73 + fi + if cmp -s "$staged_binary" "$destination"; then + printf '%s\n' "AgenNet $AGENET_RELEASE_VERSION is already installed." + print_next + exit 0 + fi + fail 'ExistingAgenNetBinaryDiffers' 73 +fi + +pending_destination=$(mktemp "$bin_dir/.agenet.XXXXXX") || \ + fail 'InstallDestinationUnavailable' 73 +cp -- "$staged_binary" "$pending_destination" || fail 'InstallWriteFailed' 73 +chmod 0755 "$pending_destination" +mv -- "$pending_destination" "$destination" || fail 'InstallPublishFailed' 73 +pending_destination= + +printf '%s\n' "Installed AgenNet $AGENET_RELEASE_VERSION to ~/.local/bin/agenet" +print_next diff --git a/scripts/package-bootstrap-skill.sh b/scripts/package-bootstrap-skill.sh new file mode 100755 index 0000000..30ac9db --- /dev/null +++ b/scripts/package-bootstrap-skill.sh @@ -0,0 +1,123 @@ +#!/bin/sh +set -eu + +usage() { + cat <<'EOF' +Usage: scripts/package-bootstrap-skill.sh --version VERSION --output PATH + +Create the deterministic agenet-node-bootstrap Skill archive. +EOF +} + +version= +output= +while [ "$#" -gt 0 ]; do + case "$1" in + --version) version=${2:-}; shift 2 ;; + --output) output=${2:-}; shift 2 ;; + --help|-h) usage; exit 0 ;; + *) usage >&2; exit 64 ;; + esac +done + +[ "$version" = '0.2.0-preview.4' ] || { + printf '%s\n' 'SkillPackageFailed: UnsupportedReleaseVersion' >&2 + exit 2 +} +[ -n "$output" ] || { + printf '%s\n' 'SkillPackageFailed: MissingOutput' >&2 + exit 2 +} + +repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) +skill_root="$repo_root/skills/agenet-node-bootstrap" + +python3 - "$skill_root" "$output" <<'PY' +import gzip +import io +import os +import pathlib +import stat +import sys +import tarfile +import uuid + + +def fail(code: str) -> None: + print(f"SkillPackageFailed: {code}", file=sys.stderr) + raise SystemExit(2) + + +source = pathlib.Path(sys.argv[1]) +output = pathlib.Path(sys.argv[2]) +members = [ + ("agenet-node-bootstrap/", None, 0o755), + ("agenet-node-bootstrap/SKILL.md", "SKILL.md", 0o644), + ("agenet-node-bootstrap/agents/", None, 0o755), + ("agenet-node-bootstrap/agents/openai.yaml", "agents/openai.yaml", 0o644), + ("agenet-node-bootstrap/references/", None, 0o755), + ( + "agenet-node-bootstrap/references/public-status.md", + "references/public-status.md", + 0o644, + ), + ("agenet-node-bootstrap/scripts/", None, 0o755), + ( + "agenet-node-bootstrap/scripts/check-public-readiness.sh", + "scripts/check-public-readiness.sh", + 0o755, + ), +] + +payloads = {} +for _, relative, _ in members: + if relative is None: + continue + path = source / relative + try: + metadata = path.lstat() + except OSError: + fail("MissingSkillFile") + if not stat.S_ISREG(metadata.st_mode) or metadata.st_size > 1024 * 1024: + fail("InvalidSkillFile") + payloads[relative] = path.read_bytes() + +parent = output.parent if str(output.parent) else pathlib.Path(".") +parent.mkdir(parents=True, exist_ok=True) +temporary = parent / f".{output.name}.{uuid.uuid4()}.tmp" +try: + with temporary.open("xb") as raw: + with gzip.GzipFile(filename="", mode="wb", fileobj=raw, mtime=0, compresslevel=9) as zipped: + with tarfile.open(fileobj=zipped, mode="w", format=tarfile.USTAR_FORMAT) as archive: + for name, relative, mode in members: + info = tarfile.TarInfo(name) + info.mode = mode + info.uid = 0 + info.gid = 0 + info.uname = "root" + info.gname = "root" + info.mtime = 0 + if relative is None: + info.type = tarfile.DIRTYPE + info.size = 0 + archive.addfile(info) + else: + body = payloads[relative] + info.type = tarfile.REGTYPE + info.size = len(body) + archive.addfile(info, io.BytesIO(body)) + raw.flush() + os.fsync(raw.fileno()) + os.replace(temporary, output) + directory_fd = os.open(parent, os.O_RDONLY) + try: + os.fsync(directory_fd) + finally: + os.close(directory_fd) +except (OSError, tarfile.TarError): + try: + temporary.unlink() + except OSError: + pass + fail("SkillArchiveWriteFailed") +PY diff --git a/scripts/package-release.sh b/scripts/package-release.sh new file mode 100755 index 0000000..8f454b4 --- /dev/null +++ b/scripts/package-release.sh @@ -0,0 +1,144 @@ +#!/bin/sh +set -eu + +repo_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) + +usage() { + printf '%s\n' \ + 'usage: scripts/package-release.sh --binary PATH --target RUST_TRIPLE' \ + ' --version VERSION --commit FULL_SHA --output-dir DIRECTORY' +} + +binary= +target= +version= +commit= +output_dir= + +while [ "$#" -gt 0 ]; do + case "$1" in + --binary) binary=${2-}; shift 2 ;; + --target) target=${2-}; shift 2 ;; + --version) version=${2-}; shift 2 ;; + --commit) commit=${2-}; shift 2 ;; + --output-dir) output_dir=${2-}; shift 2 ;; + --help|-h) usage; exit 0 ;; + *) usage >&2; exit 64 ;; + esac +done + +if [ -z "$binary" ] || [ -z "$target" ] || [ -z "$version" ] || \ + [ -z "$commit" ] || [ -z "$output_dir" ]; then + usage >&2 + exit 64 +fi + +case "$target" in + aarch64-apple-darwin|x86_64-apple-darwin|aarch64-unknown-linux-gnu|x86_64-unknown-linux-gnu) ;; + *) printf '%s\n' 'UnsupportedReleaseTarget' >&2; exit 65 ;; +esac + +if [ "$version" != '0.2.0-preview.4' ]; then + printf '%s\n' 'UnsupportedReleaseVersion' >&2 + exit 65 +fi + +case "$commit" in + *[!0-9a-f]*|'') printf '%s\n' 'InvalidReleaseCommit' >&2; exit 65 ;; +esac +if [ "${#commit}" -ne 40 ]; then + printf '%s\n' 'InvalidReleaseCommit' >&2 + exit 65 +fi + +if [ ! -f "$binary" ] || [ ! -x "$binary" ] || [ -L "$binary" ]; then + printf '%s\n' 'InvalidReleaseBinary' >&2 + exit 66 +fi +if [ ! -f "$repo_root/LICENSE" ] || [ ! -f "$repo_root/README.md" ]; then + printf '%s\n' 'ReleaseSourceFilesMissing' >&2 + exit 66 +fi + +actual_version=$($binary --version 2>/dev/null || true) +if [ "$actual_version" != "agenet $version" ]; then + printf '%s\n' 'ReleaseBinaryVersionMismatch' >&2 + exit 65 +fi + +mkdir -p -- "$output_dir" +if [ ! -d "$output_dir" ] || [ -L "$output_dir" ]; then + printf '%s\n' 'InvalidReleaseOutputDirectory' >&2 + exit 66 +fi + +root="agenet-v${version}-${target}" +archive_name="${root}.tar.gz" +staging=$(mktemp -d "${TMPDIR:-/tmp}/agenet-package.XXXXXX") +temporary_archive=$(mktemp "$output_dir/.${archive_name}.XXXXXX") +cleanup() { + rm -rf -- "$staging" + rm -f -- "$temporary_archive" +} +trap cleanup EXIT HUP INT TERM + +mkdir "$staging/$root" +cp -- "$binary" "$staging/$root/agenet" +cp -- "$repo_root/LICENSE" "$staging/$root/LICENSE" +cp -- "$repo_root/README.md" "$staging/$root/README.md" +chmod 0755 "$staging/$root" "$staging/$root/agenet" +chmod 0644 "$staging/$root/LICENSE" "$staging/$root/README.md" + +printf '%s\n' \ + "{\"format\":\"agenet.release-metadata.v1\",\"git_commit\":\"$commit\",\"target\":\"$target\",\"version\":\"$version\"}" \ + >"$staging/$root/RELEASE-METADATA.json" +chmod 0644 "$staging/$root/RELEASE-METADATA.json" + +python3 - "$staging" "$root" "$temporary_archive" <<'PY' +import gzip +import io +import pathlib +import sys +import tarfile + +staging = pathlib.Path(sys.argv[1]) +root = sys.argv[2] +output = pathlib.Path(sys.argv[3]) +members = [ + (root, 0o755, None), + (f"{root}/agenet", 0o755, staging / root / "agenet"), + (f"{root}/LICENSE", 0o644, staging / root / "LICENSE"), + (f"{root}/README.md", 0o644, staging / root / "README.md"), + ( + f"{root}/RELEASE-METADATA.json", + 0o644, + staging / root / "RELEASE-METADATA.json", + ), +] + +with output.open("wb") as raw: + with gzip.GzipFile(filename="", mode="wb", fileobj=raw, mtime=0) as compressed: + with tarfile.open(fileobj=compressed, mode="w", format=tarfile.USTAR_FORMAT) as archive: + for name, mode, source in members: + info = tarfile.TarInfo(name) + info.mode = mode + info.uid = 0 + info.gid = 0 + info.uname = "root" + info.gname = "root" + info.mtime = 0 + if source is None: + info.type = tarfile.DIRTYPE + archive.addfile(info) + continue + body = source.read_bytes() + info.type = tarfile.REGTYPE + info.size = len(body) + archive.addfile(info, io.BytesIO(body)) +PY + +chmod 0644 "$temporary_archive" +mv -f -- "$temporary_archive" "$output_dir/$archive_name" +trap - EXIT HUP INT TERM +rm -rf -- "$staging" +printf '%s\n' "$output_dir/$archive_name" diff --git a/scripts/preflight-preview-release.sh b/scripts/preflight-preview-release.sh new file mode 100755 index 0000000..d899766 --- /dev/null +++ b/scripts/preflight-preview-release.sh @@ -0,0 +1,150 @@ +#!/bin/sh +set -eu + +fail() { + printf '%s\n' "PreviewReleasePreflightFailed: $1" >&2 + exit 2 +} + +if [ "$#" -ne 0 ]; then + fail 'PreflightAcceptsNoArguments' +fi + +repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) +cd "$repo_root" +version='0.2.0-preview.4' + +for required in \ + LICENSE \ + README.md \ + Cargo.lock \ + schemas/release-manifest-v1.schema.json \ + scripts/install.sh.template \ + scripts/verify-release.sh \ + scripts/package-bootstrap-skill.sh \ + docs/install/index.md \ + docs/install/index.en.md \ + docs/bootstrap/agent-node-setup.md \ + docs/bootstrap/agent-node-setup.en.md \ + docs/releases/v0.2.0-preview.4.md \ + skills/agenet-node-bootstrap/SKILL.md \ + skills/agenet-node-bootstrap/agents/openai.yaml \ + skills/agenet-node-bootstrap/references/public-status.md \ + skills/agenet-node-bootstrap/scripts/check-public-readiness.sh; do + [ -f "$required" ] && [ ! -L "$required" ] && [ -s "$required" ] \ + || fail 'MissingReleaseSurface' +done + +python3 - "$version" <<'PY' || exit $? +import json +import pathlib +import re +import subprocess +import sys + + +def fail(code: str) -> None: + print(f"PreviewReleasePreflightFailed: {code}", file=sys.stderr) + raise SystemExit(2) + + +version = sys.argv[1] +metadata = json.loads( + subprocess.check_output( + ["cargo", "metadata", "--locked", "--no-deps", "--format-version", "1"], + text=True, + ) +) +root_packages = [package for package in metadata["packages"] if package["name"] == "agenet"] +if len(root_packages) != 1 or root_packages[0]["version"] != version: + fail("CargoVersionMismatch") + +required_version_files = [ + "README.md", + "Cargo.toml", + "Cargo.lock", + ".github/workflows/release.yml", + "src/release/manifest.rs", + "docs/install/index.md", + "docs/install/index.en.md", + "docs/bootstrap/agent-node-setup.md", + "docs/bootstrap/agent-node-setup.en.md", + "docs/releases/v0.2.0-preview.4.md", + "skills/agenet-node-bootstrap/SKILL.md", + "skills/agenet-node-bootstrap/agents/openai.yaml", + "skills/agenet-node-bootstrap/scripts/check-public-readiness.sh", +] +for name in required_version_files: + if version not in pathlib.Path(name).read_text(encoding="utf-8"): + fail("PublicVersionMismatch") + +workflow = pathlib.Path(".github/workflows/release.yml").read_text(encoding="utf-8") +uses = re.findall(r"^\s*uses:\s*([^\s#]+)", workflow, flags=re.MULTILINE) +if not uses or any(not re.search(r"@[0-9a-f]{40}$", use) for use in uses): + fail("UnpinnedGitHubAction") +if "/latest/" in workflow or "/heads/" in workflow: + fail("MovingReleaseReference") + +public = "\n".join(pathlib.Path(name).read_text(encoding="utf-8") for name in required_version_files) +for claim in [ + "physical acceptance complete", + "production-ready", + "is a stable release", + "native Windows is supported", +]: + if claim.lower() in public.lower(): + fail("UnsupportedPublicClaim") +if "Developer Preview — physical acceptance pending" not in public: + fail("MissingPreviewBoundary") + +tracked = subprocess.check_output(["git", "ls-files", "-z"]).split(b"\0") +for raw_name in tracked: + if not raw_name: + continue + name = raw_name.decode("utf-8") + path = pathlib.Path(name) + lowered = name.lower() + if ( + lowered == ".env" + or lowered.endswith((".key", ".pem", ".token", ".jsonl")) + or "/.local/" in f"/{lowered}/" + ): + fail("TrackedPrivateArtifact") + try: + body = path.read_bytes() + except OSError: + fail("TrackedFileReadFailed") + if len(body) > 4 * 1024 * 1024 or b"\0" in body: + continue + text = body.decode("utf-8", errors="ignore") + for line in text.splitlines(): + stripped = line.strip() + if stripped.startswith("-----BEGIN ") and stripped.endswith("PRIVATE KEY-----"): + fail("TrackedPrivateKey") + if re.search(r"(?:ghp_|github_pat_)[A-Za-z0-9_]{20,}", text): + fail("TrackedGitHubCredential") + if re.search(r"sk-(?:proj-)?[A-Za-z0-9_-]{20,}", text): + fail("TrackedModelCredential") +PY + +for executable in \ + scripts/package-release.sh \ + scripts/check-release-archive.sh \ + scripts/verify-release.sh \ + scripts/package-bootstrap-skill.sh \ + skills/agenet-node-bootstrap/scripts/check-public-readiness.sh; do + [ -x "$executable" ] || fail 'ReleaseScriptNotExecutable' +done + +cargo test --quiet --test release_manifest --test installer --test public_guides >/dev/null +bash tests/scripts/release-archive.sh >/dev/null +bash tests/scripts/installer-smoke.sh >/dev/null +bash tests/scripts/skill-readiness.sh >/dev/null + +if git diff --check -- . ':(exclude)docs/design/agenet-v0.1.md' >/dev/null 2>&1; then + : +else + fail 'ReleaseDiffWhitespaceError' +fi + +printf '%s\n' "AgenNet v$version release preflight passed" diff --git a/scripts/verify-release.sh b/scripts/verify-release.sh new file mode 100755 index 0000000..1263292 --- /dev/null +++ b/scripts/verify-release.sh @@ -0,0 +1,207 @@ +#!/bin/sh +set -eu + +usage() { + cat <<'EOF' +Usage: scripts/verify-release.sh \ + --manifest PATH --installer PATH --checksums PATH --archives-dir DIR \ + --version VERSION --commit FULL_SHA [--renderer PATH] \ + [--complete --release-dir DIR] + +Verify the complete offline AgenNet installer release core. The renderer must +be the agenet-render-installer binary built from the same source commit. +EOF +} + +fail() { + printf '%s\n' "ReleaseVerificationFailed: $1" >&2 + exit 2 +} + +manifest= +installer= +checksums= +archives_dir= +version= +commit= +renderer= +complete=0 +release_dir= + +while [ "$#" -gt 0 ]; do + case "$1" in + --manifest) manifest=${2:-}; shift 2 ;; + --installer) installer=${2:-}; shift 2 ;; + --checksums) checksums=${2:-}; shift 2 ;; + --archives-dir) archives_dir=${2:-}; shift 2 ;; + --version) version=${2:-}; shift 2 ;; + --commit) commit=${2:-}; shift 2 ;; + --renderer) renderer=${2:-}; shift 2 ;; + --complete) complete=1; shift ;; + --release-dir) release_dir=${2:-}; shift 2 ;; + --help|-h) usage; exit 0 ;; + *) usage >&2; fail 'InvalidArguments' ;; + esac +done + +[ -n "$manifest" ] || fail 'MissingManifest' +[ -n "$installer" ] || fail 'MissingInstaller' +[ -n "$checksums" ] || fail 'MissingChecksums' +[ -n "$archives_dir" ] || fail 'MissingArchivesDirectory' +[ -n "$version" ] || fail 'MissingVersion' +[ -n "$commit" ] || fail 'MissingCommit' + +case "$commit" in + *[!0-9a-f]*|'') fail 'InvalidCommit' ;; +esac +[ "${#commit}" -eq 40 ] || fail 'InvalidCommit' +[ -d "$archives_dir" ] && [ ! -L "$archives_dir" ] || fail 'InvalidArchivesDirectory' +if [ "$complete" -eq 1 ]; then + [ -n "$release_dir" ] || fail 'MissingReleaseDirectory' + [ -d "$release_dir" ] && [ ! -L "$release_dir" ] || fail 'InvalidReleaseDirectory' +fi + +if [ -z "$renderer" ]; then + repo_root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd) + renderer="$repo_root/target/debug/agenet-render-installer" +fi +[ -x "$renderer" ] && [ ! -L "$renderer" ] || fail 'MissingInstallerRenderer' + +temporary=$(mktemp -d "${TMPDIR:-/tmp}/agenet-release-verify.XXXXXX") \ + || fail 'TemporaryDirectoryFailed' +cleanup() { + rm -rf -- "$temporary" +} +trap cleanup EXIT HUP INT TERM + +expected_installer="$temporary/install.sh" +if ! "$renderer" --manifest "$manifest" --output "$expected_installer"; then + fail 'InvalidReleaseManifest' +fi +cmp -s "$expected_installer" "$installer" || fail 'InstallerManifestMismatch' + +python3 - "$manifest" "$installer" "$checksums" "$archives_dir" \ + "$version" "$commit" "$complete" "${release_dir:-$archives_dir}" <<'PY' || exit $? +import hashlib +import json +import pathlib +import stat +import sys + + +def fail(code: str) -> None: + print(f"ReleaseVerificationFailed: {code}", file=sys.stderr) + raise SystemExit(2) + + +def regular_file(path: pathlib.Path, maximum: int) -> bytes: + try: + metadata = path.lstat() + except OSError: + fail("MissingReleaseFile") + if not stat.S_ISREG(metadata.st_mode) or metadata.st_size > maximum: + fail("InvalidReleaseFile") + try: + return path.read_bytes() + except OSError: + fail("ReleaseFileReadFailed") + + +manifest_path = pathlib.Path(sys.argv[1]) +installer_path = pathlib.Path(sys.argv[2]) +checksums_path = pathlib.Path(sys.argv[3]) +archives_dir = pathlib.Path(sys.argv[4]) +version = sys.argv[5] +commit = sys.argv[6] +complete = sys.argv[7] == "1" +release_dir = pathlib.Path(sys.argv[8]) + +manifest_bytes = regular_file(manifest_path, 256 * 1024) +installer_bytes = regular_file(installer_path, 256 * 1024) +checksums_bytes = regular_file(checksums_path, 64 * 1024) +try: + manifest = json.loads(manifest_bytes) +except (UnicodeDecodeError, json.JSONDecodeError): + fail("InvalidReleaseManifest") + +if manifest.get("version") != version or manifest.get("git_commit") != commit: + fail("ReleaseIdentityMismatch") +artifacts = manifest.get("artifacts") +if not isinstance(artifacts, dict) or set(artifacts) != { + "macos-arm64", + "macos-x86_64", + "linux-arm64", + "linux-x86_64", +}: + fail("IncompleteReleaseArtifacts") + +files = { + manifest_path.name: manifest_bytes, + installer_path.name: installer_bytes, +} +for artifact in artifacts.values(): + if not isinstance(artifact, dict): + fail("InvalidReleaseManifest") + name = artifact.get("file_name") + digest = artifact.get("sha256") + size = artifact.get("size_bytes") + if not isinstance(name, str) or pathlib.PurePath(name).name != name: + fail("InvalidArchiveName") + archive_bytes = regular_file(archives_dir / name, 512 * 1024 * 1024) + if len(archive_bytes) != size: + fail("ReleaseArchiveSizeMismatch") + if hashlib.sha256(archive_bytes).hexdigest() != digest: + fail("ReleaseChecksumMismatch") + files[name] = archive_bytes + +if complete: + for name, maximum in { + "agent-bootstrap.md": 256 * 1024, + "agent-bootstrap.en.md": 256 * 1024, + f"agenet-node-bootstrap-v{version}.tar.gz": 16 * 1024 * 1024, + f"release-notes-v{version}.md": 256 * 1024, + }.items(): + files[name] = regular_file(release_dir / name, maximum) + +try: + lines = checksums_bytes.decode("ascii").splitlines() +except UnicodeDecodeError: + fail("InvalidChecksumFile") +parsed = {} +for line in lines: + if len(line) < 67 or line[64:66] != " ": + fail("InvalidChecksumFile") + digest, name = line[:64], line[66:] + if ( + len(digest) != 64 + or any(character not in "0123456789abcdef" for character in digest) + or pathlib.PurePath(name).name != name + or name in parsed + ): + fail("InvalidChecksumFile") + parsed[name] = digest +if set(parsed) != set(files): + fail("ChecksumFileSetMismatch") +if lines != sorted(lines, key=lambda line: line[66:]): + fail("ChecksumFileOrderMismatch") +for name, body in files.items(): + if hashlib.sha256(body).hexdigest() != parsed[name]: + fail("ReleaseChecksumMismatch") +PY + +for target in \ + aarch64-apple-darwin \ + x86_64-apple-darwin \ + aarch64-unknown-linux-gnu \ + x86_64-unknown-linux-gnu; do + archive="$archives_dir/agenet-v$version-$target.tar.gz" + scripts_dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) + "$scripts_dir/check-release-archive.sh" \ + --archive "$archive" \ + --target "$target" \ + --version "$version" \ + --commit "$commit" \ + --skip-binary-smoke >/dev/null || fail 'InvalidReleaseArchive' +done + +printf '%s\n' 'AgenNet release core verified' diff --git a/scripts/verify-two-device-evidence.sh b/scripts/verify-two-device-evidence.sh new file mode 100755 index 0000000..7d239c6 --- /dev/null +++ b/scripts/verify-two-device-evidence.sh @@ -0,0 +1,20 @@ +#!/bin/sh +set -eu + +if [ "$#" -ne 1 ]; then + printf '%s\n' 'usage: scripts/verify-two-device-evidence.sh EXACT_EVIDENCE_PATH' >&2 + exit 64 +fi + +evidence_path=$1 +if [ -x ./target/debug/agenet ]; then + exec ./target/debug/agenet evidence verify "$evidence_path" --output json +fi + +if ! command -v cargo >/dev/null 2>&1; then + printf '%s\n' 'EvidenceVerifierUnavailable' >&2 + exit 69 +fi + +export CARGO_NET_OFFLINE=true +exec cargo run --quiet --locked -- evidence verify "$evidence_path" --output json diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 0000000..220290e --- /dev/null +++ b/site/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage +*.tsbuildinfo + +# next.js +/.next/ +/.vinext/ +/out/ + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +/dist/ +/.wrangler/ +/outputs/ +/work/ diff --git a/site/.openai/hosting.json b/site/.openai/hosting.json new file mode 100644 index 0000000..5edbed0 --- /dev/null +++ b/site/.openai/hosting.json @@ -0,0 +1,5 @@ +{ + "d1": null, + "r2": null, + "project_id": "appgprj_6a804ef139688191beaf9e697f0d5967" +} diff --git a/site/README.md b/site/README.md new file mode 100644 index 0000000..4ad0e4e --- /dev/null +++ b/site/README.md @@ -0,0 +1,22 @@ +# AgenNet public site + +The bilingual AgenNet landing page and documentation site. It uses Vinext, +Vite, React, and the OpenAI Sites hosting adapter. + +Public release facts are not independently maintained here. Before every +build, `scripts/sync-public-content.mjs` reads the canonical installation and +Agent bootstrap guides from the repository root and regenerates +`app/generated/public-content.ts`. The build fails if the bilingual installer +blocks diverge, lose their fixed release, lose checksum verification, or use a +pipe-to-shell pattern. + +```sh +npm ci +npm test +npm run lint +``` + +The particle field uses WebGL2 when available. The semantic content renders on +the server, remains usable without WebGL, and becomes static when the visitor +requests reduced motion. The site has no analytics, forms, authentication, +database, secrets, D1, or R2 dependency. diff --git a/site/app/components/CopyBlock.tsx b/site/app/components/CopyBlock.tsx new file mode 100644 index 0000000..7f0e91b --- /dev/null +++ b/site/app/components/CopyBlock.tsx @@ -0,0 +1,18 @@ +"use client"; + +import { useState } from "react"; + +export function CopyBlock({ value, label }: Readonly<{ value: string; label: string }>) { + const [copied, setCopied] = useState(false); + async function copy() { + await navigator.clipboard.writeText(value); + setCopied(true); + setTimeout(() => setCopied(false), 1800); + } + return ( +
+
{label}
+
{value}
+
+ ); +} diff --git a/site/app/components/DocsPage.tsx b/site/app/components/DocsPage.tsx new file mode 100644 index 0000000..12d425d --- /dev/null +++ b/site/app/components/DocsPage.tsx @@ -0,0 +1,89 @@ +import { agentPhraseEn, agentPhraseZh, installCommand, releaseStatus, releaseVersion } from "../generated/public-content"; +import { CopyBlock } from "./CopyBlock"; +import { Locale, localized, SiteShell } from "./SiteShell"; + +export type DocKind = "index" | "install" | "bootstrap" | "architecture" | "vision" | "status"; + +const nav = { + zh: [["文档首页", ""], ["安装", "/install"], ["Agent 接入", "/bootstrap"], ["架构", "/architecture"], ["长期愿景", "/vision"], ["项目状态", "/status"]], + en: [["Overview", ""], ["Install", "/install"], ["Agent setup", "/bootstrap"], ["Architecture", "/architecture"], ["Vision", "/vision"], ["Status", "/status"]], +}; + +function DocsLayout({ locale, kind, children }: Readonly<{ locale: Locale; kind: DocKind; children: React.ReactNode }>) { + return ( + +
+ +
{children}
+
+
+ ); +} + +const zh = { + install: { + title: "校验并安装", + intro: "安装器只把固定版本的公开二进制写入 ~/.local/bin/agenet。它不会接收 Invitation、密码、私钥或模型密钥。", + }, + bootstrap: { + title: "让 Agent 把设备接入 AgenNet", + intro: "把下面这句话交给装在目标电脑上的 Agent。Agent 可以安装、检查环境并停在安全边界前;Invitation 和密码始终由人在本机 TTY 输入。", + }, +}; + +const en = { + install: { + title: "Verify and install", + intro: "The installer writes one fixed public binary to ~/.local/bin/agenet. It never accepts an Invitation, passphrase, private key, or model key.", + }, + bootstrap: { + title: "Let an Agent prepare a new AgenNet node", + intro: "Give this sentence to the Agent on the target device. It may install and inspect public readiness, but it must stop before enrollment. The human enters every Invitation and passphrase in the local TTY.", + }, +}; + +export function DocsIndex({ locale }: Readonly<{ locale: Locale }>) { + const isZh = locale === "zh"; + const cards = isZh ? [ + ["安装", "固定版本、SHA-256 校验、macOS/Linux/WSL2", "/install"], + ["Agent 节点", "一句话交给 Agent,敏感输入保留在人类 TTY", "/bootstrap"], + ["架构", "身份、路由、Contract、Evidence 与撤销", "/architecture"], + ["长期愿景", "从 Agent Network 到 Agent Society", "/vision"], + ["项目状态", "现在做到了什么,还有什么没有验证", "/status"], + ] : [ + ["Install", "Fixed release, SHA-256 verification, macOS/Linux/WSL2", "/install"], + ["Agent node", "One sentence for an Agent; secrets stay in the human TTY", "/bootstrap"], + ["Architecture", "Identity, routing, Contracts, Evidence, and revocation", "/architecture"], + ["Vision", "From Agent Network to Agent Society", "/vision"], + ["Status", "What works now and what remains unverified", "/status"], + ]; + return

DOCUMENTATION

{isZh ? "文档中心" : "Documentation"}

{isZh ? "从安装第一台节点开始,理解 AgenNet 当前可验证的能力与长期方向。" : "Start with one node, then understand AgenNet's verified capability boundary and long-term direction."}

{cards.map(([title, text, path]) =>

{title}

{text}

)}
; +} + +export function InstallDoc({ locale }: Readonly<{ locale: Locale }>) { + const c = locale === "zh" ? zh.install : en.install; + return

{releaseVersion} · FIXED RELEASE

{c.title}

{c.intro}

{releaseStatus}{locale === "zh" ? "Windows 请使用启用 systemd 的 WSL2;native Windows 暂不支持。" : "On Windows, use WSL2 with systemd. Native Windows is not supported yet."}

{locale === "zh" ? "固定安装步骤" : "Fixed installation procedure"}

{locale === "zh" ? "整段复制到本机终端。脚本先下载 install.sh 与 SHA256SUMS,核验摘要后才执行安装器。" : "Copy the complete block into the local terminal. It verifies install.sh against SHA256SUMS before execution."}

{locale === "zh" ? "公开检查" : "Public checks"}

{locale === "zh" ? "运行条件" : "Runtime requirements"}

  • macOS arm64/x86_64, Linux arm64/x86_64, or WSL2.
  • {locale === "zh" ? "Tailscale 或 WireGuard 私有覆盖网络。" : "A private Tailscale or WireGuard overlay."}
  • {locale === "zh" ? "秘密操作必须使用真实 controlling TTY。" : "Secret-bearing operations require a real controlling TTY."}
; +} + +export function BootstrapDoc({ locale }: Readonly<{ locale: Locale }>) { + const c = locale === "zh" ? zh.bootstrap : en.bootstrap; + const phrase = locale === "zh" ? agentPhraseZh : agentPhraseEn; + return

AGENT-OPERATED SETUP

{c.title}

{c.intro}

{locale === "zh" ? "一句话开始" : "Start with one sentence"}

{locale === "zh" ? "这条路径不要求目标 Agent 预先装好 Skill:固定指南会引导它安装公开二进制并遵守同一秘密边界。想预装 Skill 的用户也可以下载 release 中经过校验的确定性包。" : "The target Agent does not need the Skill in advance: the fixed guide leads it through the public install with the same secret boundary. Operators may also preinstall the deterministic, verified Skill package from the release."}

{locale === "zh" ? "下载 agenet-node-bootstrap Skill 包" : "Download the agenet-node-bootstrap Skill package"}

{locale === "zh" ? "Agent 必须停下来的地方" : "Where the Agent must stop"}

{locale === "zh" ? "秘密不进入对话" : "Secrets stay out of chat"}{locale === "zh" ? "不要把 Invitation、passphrase、私钥、token、私有地址、CIDR、原始 JSON 或终端日志发给 Agent。" : "Never give the Agent an Invitation, passphrase, private key, token, private address, CIDR, raw JSON, or terminal transcript."}

{locale === "zh" ? "Agent 能做什么" : "What the Agent may do"}

  1. {locale === "zh" ? "识别 macOS、Linux 或 WSL2 与 CPU 架构。" : "Identify macOS, Linux, or WSL2 and the CPU architecture."}
  2. {locale === "zh" ? "使用固定版本与 checksum 安装公开二进制。" : "Install the fixed public binary with checksum verification."}
  3. {locale === "zh" ? "检查 systemd user session 与私有 overlay 的公开就绪状态。" : "Check the systemd user session and public overlay readiness."}
  4. {locale === "zh" ? "在 enrollment 前停下,让人类接管 TTY。" : "Stop before enrollment and hand control to the human TTY."}
; +} + +export function ArchitectureDoc({ locale }: Readonly<{ locale: Locale }>) { + const isZh = locale === "zh"; + return

PROTOCOL v0.2

{isZh ? "协议闭环,而不是假网络" : "A real protocol loop, not a simulated network"}

{isZh ? "AgenNet 把 Agent 的推理与跨节点协作分开。模型决定需要什么能力,协议负责证明身份、限制权限并保存可审计的结果。" : "AgenNet separates Agent reasoning from cross-node coordination. Models decide what capability is needed; the protocol proves identity, limits authority, and preserves auditable outcomes."}

{[["01","Intent"],["02","Directory"],["03","Contract"],["04","Execute"],["05","Verify"],["06","Accepted"]].map(([n,t]) =>
{n}{t}
)}

{isZh ? "核心对象" : "Core objects"}

Identity

{isZh ? "Domain Root、Authority 与 Node Credential 构成可验证的签名链。" : "Domain Root, Authority, and Node Credentials form a verifiable signed chain."}

Capability

{isZh ? "Provider 只发布凭证授权范围内的具体能力。" : "Providers publish only capabilities authorized by their credential ceiling."}

Contract

{isZh ? "双方签署能力、Artifact、Grant、期限与验收规则。" : "Both parties sign the capability, artifact, grant, expiry, and acceptance rule."}

Evidence

{isZh ? "交付结果附带证据;Verifier 独立重算。" : "Delivery carries evidence; a Verifier recomputes independently."}

Revocation

{isZh ? "权限不是永久的。实时 effect 前重新检查凭证与撤销状态。" : "Authority is not permanent. Credentials and revocation are rechecked before live effects."}

Transport

{isZh ? "当前跨机边界是私有 overlay 上的 NodeId 绑定 mTLS。" : "The current cross-host boundary is NodeId-bound mTLS over a private overlay."}

{isZh ? "当前真实 workload" : "Current real workload"}

{isZh ? "source.metrics.v1 读取授权的 UTF-8 源码 Artifact,计算 SHA-256、字节数、总行数和非空行数。Executor 与 Verifier 独立计算,结果完全一致后 Requester 才签署 Accepted。" : "source.metrics.v1 reads an authorized UTF-8 source artifact and computes SHA-256, byte count, line count, and non-empty line count. Executor and Verifier compute independently before the Requester signs Accepted."}

; +} + +export function VisionDoc({ locale }: Readonly<{ locale: Locale }>) { + const isZh = locale === "zh"; + return

LONG-TERM HYPOTHESIS

{isZh ? "从 Agent Network 到 Agent Society" : "From Agent Network to Agent Society"}

{isZh ? "最终目标不是把更多 Agent 接到一张网里,而是让足够多的 Agent 与资源能够在低冲突、可纠正的制度下形成社会化协作,并尽可能高效地服务人的目标。" : "The goal is not merely to put more Agents on a network. It is to let enough Agents and resources coordinate under low-conflict, corrigible institutions and pursue human goals efficiently."}

NOWAgenNet

{isZh ? "身份、Capability、Contract、Evidence、撤销和传输语义。" : "Identity, Capability, Contract, Evidence, revocation, and transport semantics."}

NEXTAgent Network

{isZh ? "跨设备、跨所有者的异构 Agent 与确定性资源协作。" : "Cross-device, cross-owner coordination among heterogeneous Agents and deterministic resources."}

HYPOTHESISAgent Society

{isZh ? "组织、教育、知识传承、资源调度、冲突处置与公共服务。" : "Organization, education, knowledge inheritance, resource allocation, conflict resolution, and public services."}

{isZh ? "水平连接与纵向传承" : "Horizontal connection and vertical inheritance"}

{isZh ? "水平连接解决正在运行的 Agent 如何分工;纵向连接解决下一代 Agent 如何继承文明。Agent Library 保存有来源与修订记录的知识,Agent School 教方法并给出窄范围、可复验、会过期的资格,Agent Organization 围绕目标动态组建团队。未来还可能出现维修、应急、公检法和分级资源通道。" : "Horizontal links coordinate work happening now. Vertical links let future Agents inherit accumulated knowledge and methods. An Agent Library preserves sourced and revisable knowledge; an Agent School teaches methodology and issues narrow, reproducible, expiring qualifications; Agent Organizations form around goals. Maintenance, emergency response, justice, and service-class resource transit may follow."}

{isZh ? "这是一条研究路径,不是当前能力" : "This is a research path, not a current capability"}{isZh ? "连接和规模不会自动产生 AGI。真正的 Agent Society 还必须处理目标冲突、权力边界、资源稀缺、纠错、追责与多个人类 Principal 的分歧。" : "Connectivity and scale do not automatically produce AGI. A real Agent Society must still handle goal conflict, power boundaries, scarcity, correction, accountability, and disagreement among human Principals."}
; +} + +export function StatusDoc({ locale }: Readonly<{ locale: Locale }>) { + const isZh = locale === "zh"; + const works = isZh ? ["四种原生 macOS/Linux 架构与 WSL2 安装路径", "Ed25519 身份链与 NodeId 绑定 mTLS", "Capability 动态发现与双边 Contract", "独立 Verifier 与 evidence-gated Accepted", "加密 Root、撤销、续期、leave、doctor 与用户服务"] : ["Four native macOS/Linux architectures and WSL2 installation", "Ed25519 identity chains and NodeId-bound mTLS", "Dynamic Capability discovery and bilateral Contracts", "Independent verification and evidence-gated acceptance", "Encrypted Root, revocation, renewal, leave, doctor, and user services"]; + const pending = isZh ? ["尚未完成两台物理设备验收", "不是任意代码 sandbox,也不开放 shell Capability", "没有验证 Internet-scale discovery、复制状态或故障转移", "没有 quota、payment、reputation 或跨 Domain federation", "协议与持久化格式仍可能在 preview 阶段改变"] : ["Two-physical-device acceptance is still pending", "Not an arbitrary-code sandbox; no shell Capability is exposed", "No Internet-scale discovery, replicated state, or failover proof", "No quota, payment, reputation, or cross-Domain federation", "Wire and persistence formats may change during preview"]; + return

PUBLIC STATUS

{releaseVersion}

{releaseStatus}

{isZh ? "代码、安装器、Skill 与本地真实 mTLS 闭环已通过严格门禁;物理跨机证据仍待完成。" : "Code, installer, Skill, and the real local mTLS loop passed strict gates; physical cross-host evidence remains pending."}

{isZh ? "已经实现并验证" : "Implemented and verified"}

    {works.map(item =>
  • {item}
  • )}

{isZh ? "没有声称完成" : "Not claimed"}

    {pending.map(item =>
  • {item}
  • )}
{isZh ? "发布口径" : "Release posture"}{isZh ? "这是 Developer Preview,不是 stable release。Agent Society 与 collective AGI 是长期假设,不是本版本能力。" : "This is a Developer Preview, not a stable release. Agent Society and collective AGI are long-term hypotheses, not v0.2 capabilities."}
; +} diff --git a/site/app/components/LandingPage.tsx b/site/app/components/LandingPage.tsx new file mode 100644 index 0000000..d80b6fa --- /dev/null +++ b/site/app/components/LandingPage.tsx @@ -0,0 +1,107 @@ +import { releaseStatus, releaseVersion } from "../generated/public-content"; +import { ParticleField } from "./ParticleField"; +import { Locale, localized, SiteShell } from "./SiteShell"; + +const copy = { + zh: { + eyebrow: "A COORDINATION LAYER FOR AGENT SOCIETY", + title: "连接一切网络可触达的 Agent 与资源", + lead: "让独立运行、彼此陌生的智能体,通过可验证的身份、能力、合同与证据,在明确边界内完成协作。", + install: "开始安装", + docs: "阅读文档", + protocol: "不是另一个 Agent 框架", + protocolText: "AgenNet 不规定 Agent 如何思考。它处理更基础的问题:谁能发现谁、谁被允许做什么、结果如何验收,以及权限何时失效。", + journey: "从一次可信协作,到一个 Agent Society", + journeyText: "当前我们先把跨设备协作闭环做对。长期方向,是让大量异构 Agent、工具、算力、数据与设备能够低冲突地形成组织、调度资源并服务人的目标。", + honest: "愿景不是能力声明。网络规模不会自动产生 AGI;每一步都必须能被验证、推翻和纠正。", + cards: [ + ["01 / Identity", "身份先于连接", "每个参与者用签名凭证证明自己。TLS 连接、协议签名与 NodeId 三者严格绑定。"], + ["02 / Contract", "授权先于执行", "自然语言目标不会直接变成远程副作用。能力、Artifact、期限和验收条件进入双边 Contract。"], + ["03 / Evidence", "证据先于接受", "Executor 交付不等于完成。独立 Verifier 重算结果,Requester 才能签署 Accepted。"], + ], + institutions: ["Agent Library", "Agent School", "Agent Organizations", "Maintenance & Recovery", "Justice & Emergency", "Resource Transit"], + }, + en: { + eyebrow: "A COORDINATION LAYER FOR AGENT SOCIETY", + title: "Connect every network-reachable Agent and resource", + lead: "Enable independently operated Agents to collaborate through verifiable identity, capabilities, contracts, and evidence — within explicit boundaries.", + install: "Install preview", + docs: "Read the docs", + protocol: "Not another Agent framework", + protocolText: "AgenNet does not prescribe how an Agent thinks. It answers a lower-level question: who can discover whom, what is authorized, how outcomes are accepted, and when authority expires.", + journey: "From one trusted exchange to an Agent Society", + journeyText: "Today, we are making the cross-device coordination loop correct. Long term, heterogeneous Agents, tools, compute, data, and devices may form organizations, allocate resources, and pursue human goals with fewer conflicts.", + honest: "A vision is not a capability claim. Scale alone does not produce AGI; every step must remain testable, falsifiable, and corrigible.", + cards: [ + ["01 / Identity", "Identity before connectivity", "Every participant proves its identity with signed credentials. TLS, protocol signatures, and NodeId remain exactly bound."], + ["02 / Contract", "Authorization before effects", "Natural language never becomes an unbounded remote effect. Capability, artifact, expiry, and acceptance enter a bilateral Contract."], + ["03 / Evidence", "Evidence before acceptance", "Delivery is not completion. An independent Verifier recomputes the result before the Requester signs Accepted."], + ], + institutions: ["Agent Library", "Agent School", "Agent Organizations", "Maintenance & Recovery", "Justice & Emergency", "Resource Transit"], + }, +}; + +export function LandingPage({ locale }: Readonly<{ locale: Locale }>) { + const c = copy[locale]; + return ( + +
+
+ +
+
+
{c.eyebrow}
+

{c.title}

+

{c.lead}

+ + + {releaseVersion}{releaseStatus} + +
+ +
+ +
+
01
+

THE PROTOCOL LAYER

{c.protocol}

+

{c.protocolText}

+
+ +
+ {c.cards.map(([eyebrow, title, body]) => ( +
+ {eyebrow}

{title}

{body}

+ ))} +
+ +
+

ONE VERIFIABLE LOOP

Intent → Route → Contract → Execute → Verify → Accept

+
+ {["Intent", "Directory", "Contract", "Executor", "Verifier", "Accepted"].map((item, index) => ( +
{String(index + 1).padStart(2, "0")}{item}
+ ))} +
+
+ +
+
02
+

LONG-TERM RESEARCH DIRECTION

{c.journey}

{c.journeyText}

+ +
+ {c.institutions.map((item, index) =>
{String(index + 1).padStart(2, "0")}{item}
)} +
+
+ +
+

START WITH ONE NODE

+

{locale === "zh" ? "把一台设备接入网络。" : "Bring one device into the network."}
{locale === "zh" ? "让一次协作变得可信。" : "Make one exchange trustworthy."}

+ {c.install} +
+
+
+ ); +} diff --git a/site/app/components/Logo.tsx b/site/app/components/Logo.tsx new file mode 100644 index 0000000..bfa8d7d --- /dev/null +++ b/site/app/components/Logo.tsx @@ -0,0 +1,14 @@ +export function Logo() { + return ( + + + AgenNet + + ); +} diff --git a/site/app/components/ParticleField.tsx b/site/app/components/ParticleField.tsx new file mode 100644 index 0000000..5e1b04d --- /dev/null +++ b/site/app/components/ParticleField.tsx @@ -0,0 +1,110 @@ +"use client"; + +import { useEffect, useRef } from "react"; + +const vertex = `#version 300 es +precision highp float; +uniform float u_time; +uniform vec2 u_pointer; +uniform float u_pixel_ratio; +in vec3 a_position; +in float a_seed; +out float v_alpha; + +void main() { + vec3 p = a_position; + float wave = sin(p.x * 3.7 + u_time * .19 + a_seed * 5.0) * .045; + p.y += wave + cos(p.z * 4.2 - u_time * .14) * .025; + vec2 delta = p.xy - u_pointer; + float pull = exp(-dot(delta, delta) * 5.5) * .08; + p.xy += normalize(delta + .0001) * pull; + float perspective = 1.0 / (1.6 - p.z * .34); + gl_Position = vec4(p.xy * perspective, 0.0, 1.0); + gl_PointSize = (1.2 + a_seed * 1.9) * u_pixel_ratio * perspective; + v_alpha = (.2 + .65 * a_seed) * smoothstep(1.35, .15, length(p.xy)); +}`; + +const fragment = `#version 300 es +precision highp float; +in float v_alpha; +out vec4 out_color; +void main() { + vec2 p = gl_PointCoord - .5; + float d = length(p); + float core = smoothstep(.5, .04, d); + out_color = vec4(.68, .86, 1.0, core * v_alpha); +}`; + +function shader(gl: WebGL2RenderingContext, type: number, source: string) { + const value = gl.createShader(type); + if (!value) return null; + gl.shaderSource(value, source); + gl.compileShader(value); + if (!gl.getShaderParameter(value, gl.COMPILE_STATUS)) return null; + return value; +} + +export function ParticleField() { + const canvasRef = useRef(null); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas || matchMedia("(prefers-reduced-motion: reduce)").matches) return; + const gl = canvas.getContext("webgl2", { alpha: true, antialias: false, powerPreference: "high-performance" }); + if (!gl) return; + const vs = shader(gl, gl.VERTEX_SHADER, vertex); + const fs = shader(gl, gl.FRAGMENT_SHADER, fragment); + const program = gl.createProgram(); + if (!vs || !fs || !program) return; + gl.attachShader(program, vs); gl.attachShader(program, fs); gl.linkProgram(program); + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) return; + + const count = innerWidth < 700 ? 1050 : 2200; + const data = new Float32Array(count * 4); + let state = 0x4a6e6574; + const random = () => ((state = Math.imul(state ^ (state >>> 15), 1 | state) + 0x6d2b79f5) >>> 0) / 4294967296; + for (let i = 0; i < count; i += 1) { + const r = Math.sqrt(random()) * 1.55; + const angle = random() * Math.PI * 2; + data[i * 4] = Math.cos(angle) * r; + data[i * 4 + 1] = Math.sin(angle) * r * .72; + data[i * 4 + 2] = random() * 2 - 1; + data[i * 4 + 3] = random(); + } + const buffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW); + const position = gl.getAttribLocation(program, "a_position"); + const seed = gl.getAttribLocation(program, "a_seed"); + gl.enableVertexAttribArray(position); gl.vertexAttribPointer(position, 3, gl.FLOAT, false, 16, 0); + gl.enableVertexAttribArray(seed); gl.vertexAttribPointer(seed, 1, gl.FLOAT, false, 16, 12); + const time = gl.getUniformLocation(program, "u_time"); + const pointerUniform = gl.getUniformLocation(program, "u_pointer"); + const ratioUniform = gl.getUniformLocation(program, "u_pixel_ratio"); + const pointer = { x: 2, y: 2 }; + const onPointer = (event: PointerEvent) => { + pointer.x = event.clientX / innerWidth * 2 - 1; + pointer.y = -(event.clientY / innerHeight * 2 - 1); + }; + addEventListener("pointermove", onPointer, { passive: true }); + let frame = 0; + const started = performance.now(); + const draw = () => { + const ratio = Math.min(devicePixelRatio, 1.75); + const width = Math.floor(canvas.clientWidth * ratio); + const height = Math.floor(canvas.clientHeight * ratio); + if (canvas.width !== width || canvas.height !== height) { canvas.width = width; canvas.height = height; } + gl.viewport(0, 0, width, height); gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); + gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE); + gl.useProgram(program); + gl.uniform1f(time, (performance.now() - started) / 1000); + gl.uniform2f(pointerUniform, pointer.x, pointer.y); + gl.uniform1f(ratioUniform, ratio); + gl.drawArrays(gl.POINTS, 0, count); + frame = requestAnimationFrame(draw); + }; + draw(); + return () => { cancelAnimationFrame(frame); removeEventListener("pointermove", onPointer); }; + }, []); + + return