diff --git a/.dockerignore b/.dockerignore index d6cd567..9d2e1d6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,3 +8,8 @@ ci-output *.db *.db-shm *.db-wal +**/.git +**/target +**/AGENTS.md +**/work +**/ci-output diff --git a/.env.example b/.env.example deleted file mode 100644 index 3527928..0000000 --- a/.env.example +++ /dev/null @@ -1,8 +0,0 @@ -# Docker Compose reads .env; programs read exported host settings. -ASTER_PORT=8080 -ASTER_LISTEN=127.0.0.1:8080 -# JSON host information, separate from databases: -ASTER_STATE_DIR=data/host -# PostgreSQL provisioning only: credentials are not consumed by the VM host. -# Database-backed objects will receive an explicit DatabaseConfig. -POSTGRES_PASSWORD=replace-with-a-random-hex-password diff --git a/.gitea/README.md b/.gitea/README.md index 09197f3..10cc708 100644 --- a/.gitea/README.md +++ b/.gitea/README.md @@ -1,5 +1,22 @@ -# Gitea 配置 +# Aster engine automation -本目录保存仓库自己的自动化定义。目前仅有 [workflows](workflows/README.md),不保存服务器凭据或共享 Runner 配置。 +[workflows](workflows/README.md) contains repository workflows. -main 推送沿用 ces-control 验证和部署;机制见 [部署](../docs/operations/deployment.md),结果边界见 [验证](../docs/operations/validation.md)。返回 [项目入口](../README.md)。 +## CI checkout prerequisites + +All workflows use `runs-on: ces-control`; no shared Runner configuration is changed. +Configure repository or organization secrets `CODEX_BOT_SSH_KEY` (the dedicated +codex-bot private SSH key) and `GITEA_SSH_KNOWN_HOSTS` (the independently verified +known_hosts entry for `[gitea.entroia.com]:22222`). The bot needs read access to the +repository and its Aster submodule. Workflows use an isolated SSH command with +`-F /dev/null`, `IdentitiesOnly=yes`, an explicit key and port, strict host-key +checking, and temporary credential files removed after checkout. Keys are never +written into tracked files. Missing secrets fail checkout explicitly. + +The SSH submodule URL's repository owner is separate from the authenticated bot. +Local Git operations must use the same dedicated bot identity; configure the +checkout's isolated command in `GIT_SSH_COMMAND` for submodule initialization. + +Workflow definitions do not prove CI passed. CI/deployment state is not queried +automatically. Local runtime checks are not performed for Aster, Lyra or Hestia; +Tessera permits local native builds/window runs, with automated tests in CI. diff --git a/.gitea/workflows/README.md b/.gitea/workflows/README.md index c2cf76c..f508f6c 100644 --- a/.gitea/workflows/README.md +++ b/.gitea/workflows/README.md @@ -1,11 +1,5 @@ -# CI 工作流 +# Aster engine workflows -[ci-deploy.yml](ci-deploy.yml) 是当前唯一工作流:main push 或 workflow_dispatch 触发,运行于 ces-control,生产并发组串行且不取消正在运行的发布,任务超时 60 分钟。 +[ci.yml](ci.yml) verifies this project on main pushes, pull requests and manual dispatch. It runs on ces-control, with a 60-minute timeout. Checkout and secret requirements are in [the parent README](../README.md). -顺序为检出 → ci-ces-control.sh 验证及执行 Endor 示例 → 展示报告并上传 artifact → 构建 SHA 生产镜像 → main 分支执行 deploy-ces-control.sh。报告展示和上传使用 always(),验证失败也保留诊断。Artifact 名为 endor-execution-output-,包含 endor-examples.md 和 endor-examples.json,保留 30 天;从对应 Actions run 页面下载。使用 actions/upload-artifact@v3 连接当前 Gitea 的 artifact 服务,接口参考 [action 定义](https://github.com/actions/upload-artifact/blob/v3/action.yml)。 - -设置 DOCKER_BUILDKIT=0 使用当前 builder 路径,检出不保留认证信息;工作流不更改共享 Runner。PostgreSQL 保留为预置资源,基础信息由文件保存;发布只为旧基础信息做一次只读导入,不再写入数据库。 - -脚本职责见 [scripts](../../scripts/README.md),完整发布、保留数据和回退规则见 [部署](../../docs/operations/deployment.md)。main 推送会进入现有发布链,不能把推送成功写成 CI 或部署成功;按用户约定不主动跟踪状态。 - -修改步骤后同步本页及对应全局主题。返回 [Gitea 配置](../README.md)。 +There is no production service deployment in this workflow. diff --git a/.gitea/workflows/ci-deploy.yml b/.gitea/workflows/ci-deploy.yml deleted file mode 100644 index f323878..0000000 --- a/.gitea/workflows/ci-deploy.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Verify databases and deploy Aster VM - -on: - push: - branches: [main] - workflow_dispatch: - -concurrency: - group: endorphin-aster-vm-production - cancel-in-progress: false - -jobs: - verify-and-deploy: - runs-on: ces-control - timeout-minutes: 60 - env: - DOCKER_BUILDKIT: "0" - ASTER_RELEASE_SHA: ${{ gitea.sha }} - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - persist-credentials: false - - - name: Verify SQLite, PostgreSQL and host behavior - run: sh scripts/ci-ces-control.sh - - - name: Show Endor example execution output - if: always() - run: cat ci-output/endor-examples.md - - - name: Upload Endor example execution output - if: always() - uses: actions/upload-artifact@v3 - with: - name: endor-execution-output-${{ gitea.sha }} - path: | - ci-output/endor-examples.md - ci-output/endor-examples.json - if-no-files-found: error - retention-days: 30 - - - name: Build production image - run: docker build --target runtime -t "endorphin-aster-vm:$ASTER_RELEASE_SHA" . - - - name: Deploy VM host and provision PostgreSQL - if: gitea.ref == 'refs/heads/main' - run: sh scripts/deploy-ces-control.sh diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..bd1cf8e --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,49 @@ +name: Verify Aster engine + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + verify: + runs-on: ces-control + timeout-minutes: 60 + env: + DOCKER_BUILDKIT: "0" + steps: + - name: Checkout with codex-bot + env: + CODEX_BOT_SSH_KEY: ${{ secrets.CODEX_BOT_SSH_KEY }} + GITEA_SSH_KNOWN_HOSTS: ${{ secrets.GITEA_SSH_KNOWN_HOSTS }} + SOURCE_SHA: ${{ gitea.sha }} + SOURCE_REPOSITORY: ${{ gitea.repository }} + run: | + set -eu + test -n "$CODEX_BOT_SSH_KEY" + test -n "$GITEA_SSH_KNOWN_HOSTS" + case "$SOURCE_SHA" in *[!0-9a-f]*|'') exit 1;; esac + test "${#SOURCE_SHA}" -eq 40 + umask 077 + checkout_keys=$(mktemp -d) + trap 'rm -rf "$checkout_keys"' EXIT + printf '%s\n' "$CODEX_BOT_SSH_KEY" > "$checkout_keys/key" + printf '%s\n' "$GITEA_SSH_KNOWN_HOSTS" > "$checkout_keys/known_hosts" + unset CODEX_BOT_SSH_KEY GITEA_SSH_KNOWN_HOSTS + export GIT_SSH_COMMAND="ssh -F /dev/null -o IdentitiesOnly=yes -i $checkout_keys/key -p 22222 -o StrictHostKeyChecking=yes -o UserKnownHostsFile=$checkout_keys/known_hosts" + git init . + source_url="ssh://git@gitea.entroia.com:22222/$SOURCE_REPOSITORY.git" + if git remote get-url origin >/dev/null 2>&1; then + git remote set-url origin "$source_url" + else + git remote add origin "$source_url" + fi + git fetch --no-tags --depth=1 origin "$SOURCE_SHA" + git checkout --detach --force FETCH_HEAD + git submodule update --init --recursive + - name: Verify engine only + run: | + image="endorphin-aster-vm-ci:${{ gitea.sha }}" + docker build -f Dockerfile.ci -t "$image" . + docker run --rm --init "$image" diff --git a/Cargo.lock b/Cargo.lock index 0900a5b..216be5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,140 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - -[[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 = "axum" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" -dependencies = [ - "axum-core", - "bytes", - "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", - "sync_wrapper", - "tokio", - "tower", - "tower-layer", - "tower-service", -] - -[[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", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "2.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" - -[[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 = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" - -[[package]] -name = "cc" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6651c9ed80effdc7db0ff72512157f901af5e3549e341e24b1dd4887d836d838" -dependencies = [ - "find-msvc-tools", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" - -[[package]] -name = "cli" -version = "0.1.0" -dependencies = [ - "compiler", - "core", - "frontend", - "host", -] - [[package]] name = "compiler" version = "0.1.0" @@ -151,167 +17,6 @@ dependencies = [ "objects", ] -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crc" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" - -[[package]] -name = "crossbeam-queue" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[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.9", -] - -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - -[[package]] -name = "either" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" -dependencies = [ - "serde", -] - -[[package]] -name = "equivalent" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" - -[[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 = "etcetera" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - -[[package]] -name = "event-listener" -version = "5.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" -dependencies = [ - "parking", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" - -[[package]] -name = "find-msvc-tools" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" - -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "spin", -] - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[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 = "frontend" version = "0.1.0" @@ -321,525 +26,22 @@ dependencies = [ "serde_json", ] -[[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-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - -[[package]] -name = "futures-io" -version = "0.3.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" - -[[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-core", - "futures-io", - "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", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "libc", - "r-efi", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "allocator-api2", - "equivalent", - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "hashlink" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown 0.15.5", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "host" -version = "0.1.0" -dependencies = [ - "serde", - "serde_json", - "tempfile", -] - -[[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 = "hyper" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" -dependencies = [ - "atomic-waker", - "bytes", - "futures-core", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", -] - -[[package]] -name = "hyper-util" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" -dependencies = [ - "bytes", - "http", - "http-body", - "hyper", - "pin-project-lite", - "tokio", - "tower-service", -] - -[[package]] -name = "icu_collections" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" - -[[package]] -name = "icu_properties" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" - -[[package]] -name = "icu_provider" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" -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.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" -dependencies = [ - "equivalent", - "hashbrown 0.17.1", -] - [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" -[[package]] -name = "libc" -version = "0.2.191" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" - -[[package]] -name = "libredox" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" -dependencies = [ - "bitflags", - "libc", - "plain", - "redox_syscall 0.9.4", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" - -[[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.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" - -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - [[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 = "mio" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - [[package]] name = "objects" version = "0.1.0" -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.5.18", - "smallvec", - "windows-link", -] - [[package]] name = "peg" version = "0.8.6" @@ -867,48 +69,6 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7011d97b484a5ebdc4b1fdb3b12d5e4bbbea56e9d22b688f2e79e04b65a7d8a6" -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[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.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" - -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - -[[package]] -name = "potential_utf" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" -dependencies = [ - "zerovec", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "proc-macro2" version = "1.0.107" @@ -927,133 +87,6 @@ dependencies = [ "proc-macro2", ] -[[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.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[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", -] - -[[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 = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_syscall" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e" -dependencies = [ - "bitflags", -] - -[[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", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustix" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" -dependencies = [ - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-pki-types" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" -dependencies = [ - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "serde" version = "1.0.229" @@ -1097,290 +130,6 @@ dependencies = [ "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 = "server" -version = "0.1.0" -dependencies = [ - "axum", - "core", - "host", - "serde", - "tokio", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[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 = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" -dependencies = [ - "serde", -] - -[[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 = "spin" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" -dependencies = [ - "lock_api", -] - -[[package]] -name = "sqlx" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" -dependencies = [ - "base64", - "bytes", - "crc", - "crossbeam-queue", - "either", - "event-listener", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown 0.15.5", - "hashlink", - "indexmap", - "log", - "memchr", - "once_cell", - "percent-encoding", - "rustls", - "serde", - "serde_json", - "sha2", - "smallvec", - "thiserror", - "tokio", - "tokio-stream", - "tracing", - "url", - "webpki-roots 0.26.11", -] - -[[package]] -name = "sqlx-macros" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 2.0.119", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" -dependencies = [ - "dotenvy", - "either", - "heck", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2", - "sqlx-core", - "sqlx-postgres", - "sqlx-sqlite", - "syn 2.0.119", - "tokio", - "url", -] - -[[package]] -name = "sqlx-postgres" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" -dependencies = [ - "atoi", - "base64", - "bitflags", - "byteorder", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-util", - "hex", - "hkdf", - "hmac", - "home", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "rand", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" -dependencies = [ - "atoi", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "serde_urlencoded", - "sqlx-core", - "thiserror", - "tracing", - "url", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "storage" -version = "0.1.0" -dependencies = [ - "sqlx", - "tempfile", - "tokio", -] - -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - -[[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.9" @@ -1392,541 +141,12 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" - -[[package]] -name = "synstructure" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.9", -] - -[[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 = "2.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.9", -] - -[[package]] -name = "tinystr" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" - -[[package]] -name = "tokio" -version = "1.53.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce3335fa71841cda333a58d7615b03901380ecf09d59b3296d21f8bbac0dde4e" -dependencies = [ - "bytes", - "libc", - "mio", - "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.9", -] - -[[package]] -name = "tokio-stream" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[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", -] - -[[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", -] - -[[package]] -name = "typenum" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" - -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - [[package]] name = "unicode-ident" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" -[[package]] -name = "unicode-normalization" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cd3fb03d5288b3ba9a1ee103ac7e9cbb59f0b03d64f8ddfcc04dd42758a85d" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-properties" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" - -[[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", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - -[[package]] -name = "webpki-roots" -version = "0.26.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" -dependencies = [ - "webpki-roots 1.0.9", -] - -[[package]] -name = "webpki-roots" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "whoami" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "writeable" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" - -[[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.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.9", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5fe1f8f1b06191a00962174c61aa5005e0bb391a6d80d07e24d115c01a92ed8" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.63" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "863ad3ac83293fb4d740aedbfdc9240dd8d1a50c1099acd76ce80ce7c7230c7f" -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.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.9", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" - -[[package]] -name = "zerotrie" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.9", -] - [[package]] name = "zmij" version = "1.0.23" diff --git a/Cargo.toml b/Cargo.toml index 8f66fae..bb57eaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["crates/core", "crates/objects", "crates/frontend", "crates/compiler", "crates/storage", "crates/cli", "crates/server", "crates/host"] +members = ["crates/core", "crates/objects", "crates/frontend", "crates/compiler"] resolver = "2" [workspace.package] @@ -11,16 +11,11 @@ license = "MIT" [workspace.dependencies] core = { path = "crates/core" } objects = { path = "crates/objects" } -storage = { path = "crates/storage" } -host = { path = "crates/host" } frontend = { path = "crates/frontend" } compiler = { path = "crates/compiler" } peg = "=0.8.6" -axum = { version = "0.8", default-features = false, features = ["http1", "json", "tokio"] } serde = { version = "1", features = ["derive"] } serde_json = "1" -sqlx = { version = "0.8.6", default-features = false, features = ["runtime-tokio", "tls-rustls-ring", "sqlite", "postgres"] } -tokio = { version = "1", features = ["macros", "rt-multi-thread", "net", "signal", "fs"] } [workspace.lints.rust] unsafe_code = "forbid" diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index dfb99f3..0000000 --- a/Dockerfile +++ /dev/null @@ -1,31 +0,0 @@ -FROM rust:1.91.1-bookworm AS source -WORKDIR /build -ENV CARGO_BUILD_JOBS=1 -COPY Cargo.toml Cargo.lock ./ -COPY crates ./crates -COPY scripts ./scripts - -FROM source AS ci -RUN apt-get update && apt-get install -y --no-install-recommends python3 \ - && rm -rf /var/lib/apt/lists/* \ - && rustup component add rustfmt clippy -ENV CARGO_PROFILE_DEV_DEBUG=0 CARGO_PROFILE_TEST_DEBUG=0 -CMD ["sh", "scripts/check.sh"] - -FROM source AS build -RUN cargo build --locked --release -p server -p cli - -FROM debian:bookworm-slim AS runtime -RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates curl \ - && rm -rf /var/lib/apt/lists/* \ - && useradd --system --uid 10001 --create-home aster \ - && mkdir -p /data/host && chown -R aster:aster /data -COPY --from=build /build/target/release/server /usr/local/bin/server -COPY --from=build /build/target/release/cli /usr/local/bin/cli -USER 10001 -WORKDIR /data -ENV ASTER_LISTEN=0.0.0.0:8080 ASTER_STATE_DIR=/data/host -EXPOSE 8080 -HEALTHCHECK --interval=10s --timeout=6s --start-period=10s --retries=3 \ - CMD curl --fail --silent http://127.0.0.1:8080/health || exit 1 -CMD ["server"] diff --git a/Dockerfile.ci b/Dockerfile.ci new file mode 100644 index 0000000..9ec9f24 --- /dev/null +++ b/Dockerfile.ci @@ -0,0 +1,10 @@ +FROM rust:1.91.1-bookworm +WORKDIR /build +RUN apt-get update && apt-get install -y --no-install-recommends python3 \ + && rm -rf /var/lib/apt/lists/* \ + && rustup component add rustfmt clippy +ENV CARGO_BUILD_JOBS=1 CARGO_PROFILE_DEV_DEBUG=0 CARGO_PROFILE_TEST_DEBUG=0 +COPY Cargo.toml Cargo.lock ./ +COPY crates ./crates +COPY scripts ./scripts +CMD ["sh", "scripts/check.sh"] diff --git a/README.md b/README.md index ab96f21..daf0ff2 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,19 @@ -# Endor / Aster VM +# Aster VM -Rust 编写的 Endor 虚拟机实现。核心对象与对象存储位于独立 objects 包,core 专注 VM、执行帧与求值循环。当前具备 EndorObject、稳定对象分配、代际句柄、引用计数、显式循环回收,以及可执行语言函数的小型 wordcode VM。数字统一为无损表示优先的 Number。 +Aster 是可嵌入的 Endor 语言引擎,提供源码阅读、解析、AST、字节码编译、对象系统和 VM。宿主通过 Rust 库接口创建 VM;引擎不部署 HTTP 服务,也不提供应用级数据库或文件信息存储。 -Endor 采用平台解释执行模型;AST 到字节码的生成属于解释器内部准备步骤,使用者无需先构建独立程序。资源修订与依赖锁用于选择用户代码,解释器由平台统一升级;相关设计方向见 [解释执行与平台资源版本](docs/architecture/execution-and-versions.md),资源版本管理尚未实现。 - -独立 frontend 提供阅读器、字符 PEG lexer、token PEG parser、ASDL 生成的 AST 与编辑器接口。compiler 已连接名称/作用域分析、字节码生成和代码对象装载,CLI run/eval 可执行基础函数与表达式;可执行示例见 [compiler/examples](crates/compiler/examples/README.md)。VM 从空堆启动;Python type/元类体系、数值算术、源码闭包和完整异常传播尚未实现。数据库连接仅为需要数据库的特殊类保留;普通宿主不连接数据库;基础信息改用 host 包的 UTF-8 JSON 文件,不保存 VM 堆。当前边界见 [系统架构](docs/architecture/README.md)。 - -## 从哪里读 - -| 要做的事 | 入口 | +| 目录 | 职责 | | --- | --- | -| 查一个功能、设计或代码位置 | [文档总索引](docs/README.md) | -| 看解释执行、资源修订、依赖锁和平台升级的关系 | [执行与版本模型](docs/architecture/execution-and-versions.md) | -| 看核心对象与对象存储 | [objects](crates/objects/README.md) | -| 看源码目录与 crate 边界 | [crates](crates/README.md) | -| 看字符串、流、文件与平台 URI 如何进入 lexer | [Endor 阅读器](docs/runtime/reader.md) | -| 看 AST 根节点、ASDL、遍历与 JSON/编辑器接口 | [AST 模型](docs/runtime/ast.md) | -| 看源码词法、语法、AST 和解析命令 | [源码前端](docs/runtime/frontend.md) | -| 看名称/作用域分析、源码编译与执行示例 | [编译链路](docs/runtime/compiler.md) | -| 看指令、代码对象、执行帧和执行循环 | [字节码 VM](docs/runtime/bytecode.md) | -| 看 VM/帧/对象如何存活和销毁 | [生命周期](docs/runtime/lifecycle.md) | -| 看基础信息文件和旧库迁移 | [宿主信息](docs/operations/host-information.md) | -| 看配置、UTF-8 与 locale 边界 | [配置](docs/operations/configuration.md) | -| 运行 CLI/HTTP 宿主 | [运行指南](docs/operations/README.md) | -| 看 ces-control CI 与 PostgreSQL 部署 | [部署](docs/operations/deployment.md) | -| 修改代码并同步文档 | [开发与文档维护](docs/contributing/README.md) | +| [crates](crates/README.md) | objects、core、frontend、compiler 四个库 | +| [docs](docs/README.md) | 架构、语言运行时、嵌入契约与维护说明 | +| [scripts](scripts/README.md) | AST 生成与远端引擎验证 | +| [.gitea](.gitea/README.md) | ces-control 上的引擎 CI,无应用部署 | +| Dockerfile.ci | 远端验证镜像,不是运行时部署镜像 | +| Cargo.toml / Cargo.lock | 库工作区及锁定依赖;Rust 1.91+ | +| LICENSE / .gitignore / .dockerignore | 许可和产物排除规则 | -## 根目录内容 +宿主独立维护:[endorphin-lyra-cli](https://gitea.entroia.com/Zayden-Jung/endorphin-lyra-cli) 提供命令行执行与五个 Endor 示例的实际输出报告;[Hestia Server](https://gitea.entroia.com/Zayden-Jung/endorphin-hestia-server) 提供 HTTP、管理命令、宿主 JSON、数据库适配及既有 Docker 部署;Tessera GUI 提供桌面窗口和绘制。宿主以 Git 子模块固定引擎提交,再通过 Cargo 路径依赖链接所需库。 -| 文件或目录 | 职责 | -| --- | --- | -| Cargo.toml / Cargo.lock | 工作区、包依赖、Rust 版本要求及依赖锁定;包名与目录一致:core、objects、frontend、compiler、storage、host、cli、server | -| crates/ | core、objects、frontend、compiler、storage、host、cli、server 八个包,每个目录的 README 解释本目录 | -| docs/ | 跨目录的架构、运行时契约、配置、运行、部署与维护说明 | -| scripts/ | 指定远端环境中的检查、冒烟、CI 与部署脚本 | -| .gitea/ | Gitea Actions 工作流 | -| Dockerfile | source、ci、build、runtime 多阶段镜像 | -| docker-compose.yml | VM 宿主运行方案,保留原有数据卷 | -| docker-compose.postgres.yml | VM 宿主与预置 PostgreSQL | -| docker-compose.ces-control.yml | ces-control 宿主与 PostgreSQL 资源部署 | -| .env.example / .gitignore / .dockerignore | 配置样例、Git 与 Docker 上下文排除规则 | -| LICENSE | MIT 许可 | +当前已接通 reader → lexer/parser → ASDL AST → 名称分析 → wordcode → VM。EndorObject、无损 Number 表示、函数与显式引用管理已实现;数值算术、完整异常传播、可靠取消、内存配额和不可信代码沙箱仍未实现。实际入口及边界见 [文档索引](docs/README.md)。 -本地不编译、不测试、不下载依赖;可做文档和静态文件检查。运行验证交给 ces-control;按用户约定不主动跟踪 CI/部署结果。命令和使用条件见 [验证](docs/operations/validation.md)。 - -文档随代码版本更新:就地解释放各目录 README,跨目录契约放 docs,对外阅读副本以仓库文档为准。忽略的 work、target、data、ci-output 和 Git 内部目录不属于需要维护 README 的源码目录。Endor 示例实际输出由 CI 保存为 artifact,查看方式见 [验证报告](docs/operations/validation.md)。 +本地不构建或测试 Aster,不下载依赖;运行验证在 ces-control。仅保留 CI,移除旧宿主部署定义不会停止或删除现有服务、数据库、卷及生产目录。生产运维已归 Hestia;按约定不主动查询 CI/部署结果。 diff --git a/crates/README.md b/crates/README.md index 47b5bd1..dc2afae 100644 --- a/crates/README.md +++ b/crates/README.md @@ -1,20 +1,10 @@ -# Cargo 工作区 +# 引擎工作区 -包名与目录一致,路径依赖定义在根 Cargo.toml。 +| 目录 | 包及职责 | +| --- | --- | +| [objects](objects/README.md) | 零外部依赖;核心对象、私有堆、Number、代码和指令数据 | +| [core](core/README.md) | 库名 endor_core;仅依赖 objects,提供 VM、执行帧和求值循环 | +| [frontend](frontend/README.md) | Reader、PEG lexer/parser、ASDL AST 和编辑器接口 | +| [compiler](compiler/README.md) | Frontend AST 到 wordcode;依赖 frontend 和 core | -| 目录 | 包名/产物 | 职责 | -| --- | --- | --- | -| [core](core/README.md) | core,库 endor_core | VM、执行帧与字节码求值循环,仅依赖本地 objects | -| [objects](objects/README.md) | objects | 零依赖的核心对象、对象存储、代码对象与指令数据/静态验证 | -| [frontend](frontend/README.md) | frontend | Endor 阅读器、字符 PEG lexer、token PEG parser、ASDL 生成的 AST/遍历/JSON 与编辑器接口,不执行源码 | -| [compiler](compiler/README.md) | compiler | AST 名称/作用域分析、wordcode 模板生成和 VM 代码对象装载 | -| [host](host/README.md) | host | UTF-8 JSON 文件基础信息,与数据库分离 | -| [storage](storage/README.md) | storage | 为数据库特殊类保留的显式连接适配,不提供基础信息存储 | -| [cli](cli/README.md) | cli,二进制 cli | 引导演示、源码解析/执行与文件信息命令,不使用数据库 | -| [server](server/README.md) | server,二进制 server | HTTP 宿主与文件版本信息,不使用数据库 | - -CLI 依赖 Compiler、Core、Frontend 与 Host,Server 依赖 Core 与 Host,二者不依赖 Storage;Compiler 依赖 Frontend 与 Core;Frontend 依赖 peg、serde/serde_json,不依赖 Core;Core 只依赖本地 Objects;Objects 不依赖 Core 或第三方包,二者均不依赖 Storage。未来特殊类的宿主实现按需依赖 Storage,见 [数据库对象](../docs/runtime/database-objects.md)。 - -Endor 的解释执行流程与平台资源版本方向见 [执行与版本模型](../docs/architecture/execution-and-versions.md)。Compiler 已连接 Frontend 与 Core 的最小源码执行链路,见 [编译契约](../docs/runtime/compiler.md);源码闭包、数值算术、版本锁、平台资源后端和持久化字节码缓存仍未实现。 - -core 的 Rust 库名为 endor_core,避免与标准库 core 重名,见 [Cargo 目标说明](https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-name-field)。网络、数据库、进程配置和信号不进入 Core。全局说明见 [架构](../docs/architecture/README.md),返回 [项目入口](../README.md)。 +依赖方向为 compiler → frontend + core、core → objects;frontend 依赖 peg、serde 和 serde_json。网络、数据库、进程配置和 GUI 能力由独立宿主提供。CLI、Server、Host、Storage 已移到独立宿主仓库。返回 [项目入口](../README.md)。 diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml deleted file mode 100644 index c53b3d7..0000000 --- a/crates/cli/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "cli" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true - -[[bin]] -name = "cli" -path = "src/main.rs" - -[dependencies] -compiler.workspace = true -core.workspace = true -frontend.workspace = true -host.workspace = true - -[lints] -workspace = true diff --git a/crates/cli/README.md b/crates/cli/README.md deleted file mode 100644 index 23c1d91..0000000 --- a/crates/cli/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# cli - -同步命令行宿主,提供 bootstrap、parse、run、eval、help 和文件 metadata 命令。bootstrap 演示两个运行时对象并释放,不读取数据库配置、不连接或写入数据库。 - -| 内容 | 职责 | -| --- | --- | -| Cargo.toml | cli 二进制与 compiler/core/frontend/host 依赖;无 storage 或 Tokio 依赖 | -| [src](src/README.md) | 参数路由、引导演示、文件/stdin 阅读、源码解析/执行和退出码 | -| [tests](tests/README.md) | parse 的 AST/JSON 与 run/eval 的文件/stdin 执行、错误和退出码 | - -运行方式见 [运行指南](../../docs/operations/README.md),输入边界见 [配置](../../docs/operations/configuration.md)。parse 通过 EndorReader 严格读取 UTF-8 文件或 `-` 指定的 stdin 并输出 AstRoot;--mode 选择 module/expression/interactive,--json 输出带 schema 的 AST JSON,不执行源码,见 [前端](../../docs/runtime/frontend.md);run 调用模块的无参数入口(默认 main),eval 执行独立表达式,见 [编译链路](../../docs/runtime/compiler.md);metadata put/get 和只导入缺失项的 import 均使用文件,见 [宿主信息](../../docs/operations/host-information.md)。返回 [工作区](../README.md)。 diff --git a/crates/cli/src/README.md b/crates/cli/src/README.md deleted file mode 100644 index 31c24be..0000000 --- a/crates/cli/src/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# CLI 入口 - -[main.rs](main.rs) 是二进制入口:同步 main 将 run 的结果转成进程退出码;run 路由 bootstrap、parse、源码 run/eval、help、文件 metadata put/get/import,错误打印到 stderr 并返回失败。 - -bootstrap 创建并显式释放两个对象,不读取数据库环境、不打开数据库。parse_command 使用 EndorReader 和 parse_source_ast 输出 AstRoot;--mode 选择 module/expression/interactive(默认 module),--json 通过 AstDocument 输出 JSON,格式见 [AST 模型](../../../docs/runtime/ast.md);`-` 从 stdin 读到 EOF,来源为 ``。词法/语法错误显示来源、行、列;不创建 VM 或执行源码。未知命令报错;metadata 通过 host::MetadataStore 读写文件,import 从标准输入接收旧记录且不覆盖已有文件。参数目前由 std::env::args 读取,非 Unicode 参数可能 panic,边界见 [配置](../../../docs/operations/configuration.md)。 - -[execute.rs](execute.rs) 负责 run/eval 的文件/stdin 阅读、调用 compiler、创建 VM/globals、模块初始化与入口调用、结果格式化、traceback 和成功/失败后的根释放;命名空间由 CLI 自己创建并清空,见 [编译契约](../../../docs/runtime/compiler.md)。 - -新增命令时更新用法、[运行指南](../../../docs/operations/README.md) 和 [smoke.py 的导航](../../../scripts/README.md)。返回 [cli](../README.md)。 diff --git a/crates/cli/src/execute.rs b/crates/cli/src/execute.rs deleted file mode 100644 index 8e92486..0000000 --- a/crates/cli/src/execute.rs +++ /dev/null @@ -1,105 +0,0 @@ -use std::error::Error; - -use compiler::{CompileOptions, compile_source}; -use endor_core::{Value, Vm}; -use frontend::{EndorReader, ParseMode, SourceUnit}; - -pub(super) fn run_command(arguments: &[&str]) -> Result<(), Box> { - let mut entry = "main"; - let mut path = None; - let mut arguments = arguments.iter().copied(); - while let Some(argument) = arguments.next() { - match argument { - "--entry" => { - entry = arguments - .next() - .filter(|name| !name.is_empty() && !name.starts_with("--")) - .ok_or(super::USAGE)?; - } - value if !value.starts_with("--") && path.is_none() => path = Some(value), - _ => return Err(super::USAGE.into()), - } - } - let source = read(path.ok_or(super::USAGE)?)?; - let output = execute(&source, ParseMode::Module, Some(entry))?; - println!("{output}"); - Ok(()) -} - -pub(super) fn eval_command(path: &str) -> Result<(), Box> { - let source = read(path)?; - let output = execute(&source, ParseMode::Expression, None)?; - println!("{output}"); - Ok(()) -} - -fn read(path: &str) -> Result> { - Ok(if path == "-" { - EndorReader::from_stream("", std::io::stdin().lock())? - } else { - EndorReader::from_file(path)? - }) -} - -fn execute( - source: &SourceUnit, - mode: ParseMode, - entry: Option<&str>, -) -> Result> { - let compiled = compile_source(source, mode, &CompileOptions::default())?; - let mut vm = Vm::new()?; - let code = compiled.load(&mut vm)?; - let globals = match vm.new_dict() { - Ok(globals) => globals, - Err(error) => { - vm.release(code)?; - return Err(error.into()); - } - }; - let result = (|| -> Result> { - let mut value = vm.execute(code, globals)?; - if let Some(entry) = entry { - vm.release_value(value)?; - let Some(Value::Object(function)) = vm.dict_get(globals, &Value::Text(entry.into()))? - else { - return Err(format!("entry function not found: {entry}").into()); - }; - value = vm.call(function, &[])?; - } - let output = format_value(&vm, &value); - vm.release_value(value)?; - output - })(); - // Copy traceback metadata before cleanup. This is the entry location of - // each code object plus bytecode offset, not a per-instruction source map. - let result = result.map_err(|error| { - let mut message = format!("{}: {error}", source.origin()); - for location in vm.last_traceback() { - message.push_str(&format!( - "\n at {} ({}:{}, code unit {})", - location.function, - location.filename, - location.first_line, - location.instruction_offset - )); - } - Box::::from(message) - }); - vm.release(code)?; - // CLI owns this fresh namespace. Clearing it breaks function↔globals - // cycles on both success and failure; embedders choose their own lifetime. - vm.dict_clear(globals)?; - vm.release(globals)?; - vm.collect_cycles(); - result -} - -fn format_value(vm: &Vm, value: &Value) -> Result> { - Ok(match value { - Value::Nil => "nil".into(), - Value::Bool(value) => value.to_string(), - Value::Text(value) => value.clone(), - Value::Number(value) => value.to_string(), - Value::Object(reference) => vm.str(*reference)?, - }) -} diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs deleted file mode 100644 index a0f2c4e..0000000 --- a/crates/cli/src/main.rs +++ /dev/null @@ -1,110 +0,0 @@ -use std::error::Error; -use std::process::ExitCode; - -use endor_core::{Value, Vm}; -use frontend::ast::AstDocument; -use frontend::{EndorReader, ParseMode, parse_source_ast}; -use host::MetadataStore; - -mod execute; - -const USAGE: &str = "Usage: cli [bootstrap | run [--entry name] | eval | parse [--mode module|expression|interactive] [--json] | metadata put | metadata get | metadata import | help]"; - -fn main() -> ExitCode { - match run() { - Ok(()) => ExitCode::SUCCESS, - Err(error) => { - eprintln!("cli: {error}"); - ExitCode::FAILURE - } - } -} - -fn run() -> Result<(), Box> { - let args: Vec = std::env::args().skip(1).collect(); - match args - .iter() - .map(String::as_str) - .collect::>() - .as_slice() - { - [] | ["bootstrap"] => { - let mut vm = Vm::new()?; - let first = vm.new_object()?; - let second = vm.new_object()?; - vm.set_attr( - first, - "message", - Value::Text("Hello from EndorObject".into()), - )?; - println!("runtime=EndorObject type_system=disabled"); - println!( - "objects_distinct={} heap_objects={}", - first != second, - vm.object_count() - ); - vm.release(first)?; - vm.release(second)?; - println!("released heap_objects={}", vm.object_count()); - println!("bootstrap uses no database"); - } - ["help"] | ["--help"] | ["-h"] => println!("{USAGE}"), - ["parse", arguments @ ..] => parse_command(arguments)?, - ["run", arguments @ ..] => execute::run_command(arguments)?, - ["eval", path] => execute::eval_command(path)?, - ["metadata", "put", key, value] => { - MetadataStore::from_env()?.put(key, value)?; - println!("stored (file)"); - } - ["metadata", "get", key] => { - println!( - "{}", - MetadataStore::from_env()? - .get(key)? - .as_deref() - .unwrap_or("(missing)") - ); - } - ["metadata", "import"] => { - let count = MetadataStore::from_env()?.import_missing(std::io::stdin().lock())?; - println!("imported {count} missing entries (file)"); - } - _ => return Err(USAGE.into()), - } - Ok(()) -} - -fn parse_command(arguments: &[&str]) -> Result<(), Box> { - let mut mode = ParseMode::Module; - let mut json = false; - let mut path = None; - let mut arguments = arguments.iter().copied(); - while let Some(argument) = arguments.next() { - match argument { - "--json" => json = true, - "--mode" => { - mode = match arguments.next() { - Some("module") => ParseMode::Module, - Some("expression") => ParseMode::Expression, - Some("interactive") => ParseMode::Interactive, - _ => return Err(USAGE.into()), - } - } - value if !value.starts_with("--") && path.is_none() => path = Some(value), - _ => return Err(USAGE.into()), - } - } - let path = path.ok_or(USAGE)?; - let source = if path == "-" { - EndorReader::from_stream("", std::io::stdin().lock())? - } else { - EndorReader::from_file(path)? - }; - let root = parse_source_ast(&source, mode)?; - if json { - println!("{}", AstDocument::new(root)?.to_json()?); - } else { - println!("{root:#?}"); - } - Ok(()) -} diff --git a/crates/cli/tests/README.md b/crates/cli/tests/README.md deleted file mode 100644 index 8105067..0000000 --- a/crates/cli/tests/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# CLI 命令检查 - -[parse.rs](parse.rs) 使用 Cargo 提供的 cli 测试二进制检查 parse 命令:合法 UTF-8 源码输出 AST 且不执行名称调用;非法语法输出文件/行/列并返回失败;非法 UTF-8 不替换字符;`parse -` 检查 stdin 输入、EOF 和带流名的错误;--json/--mode 检查表达式与交互根、schema 标识以及非法模式退出。 - -[execute.rs](execute.rs) 检查 compiler/examples 中的真实 Endor 文件、run/eval、显式入口、stdin、运行时 traceback、名称/编译/语法/UTF-8 错误和无成功输出的失败退出。 - -测试仅在 ces-control 的工作区检查中运行,本地不执行。测试临时文件使用独立名称并在结束时清理。返回 [cli](../README.md),验证约定见 [验证](../../../docs/operations/validation.md)。 diff --git a/crates/cli/tests/execute.rs b/crates/cli/tests/execute.rs deleted file mode 100644 index c5e04c9..0000000 --- a/crates/cli/tests/execute.rs +++ /dev/null @@ -1,136 +0,0 @@ -use std::io::Write; -use std::process::{Command, Output, Stdio}; - -fn stdin_command(arguments: &[&str], source: &[u8]) -> Output { - let mut child = Command::new(env!("CARGO_BIN_EXE_cli")) - .args(arguments) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - child.stdin.take().unwrap().write_all(source).unwrap(); - child.wait_with_output().unwrap() -} - -#[test] -fn run_executes_real_endor_files_and_prints_entry_results() { - for (file, expected) in [ - ("hello.endor", "你好,Endor 🌱\n"), - ("calls.endor", "函数调用已接通\n"), - ("shadowing.endor", "局部名称优先\n"), - ("number.endor", "-123456789012345678901234567890.00100\n"), - ] { - let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) - .join("../compiler/examples") - .join(file); - let output = Command::new(env!("CARGO_BIN_EXE_cli")) - .arg("run") - .arg(path) - .output() - .unwrap(); - assert!( - output.status.success(), - "{}", - String::from_utf8_lossy(&output.stderr) - ); - assert_eq!(String::from_utf8(output.stdout).unwrap(), expected); - assert!(output.stderr.is_empty()); - } -} - -#[test] -fn eval_and_run_read_stdin_and_support_explicit_entry_names() { - let output = stdin_command(&["eval", "-"], "\"表达式🌱\"".as_bytes()); - assert!( - output.status.success(), - "{}", - String::from_utf8_lossy(&output.stderr) - ); - assert_eq!(String::from_utf8(output.stdout).unwrap(), "表达式🌱\n"); - let output = stdin_command( - &["run", "--entry", "greet", "-"], - b"function greet(): String { return \"ok\" }", - ); - assert!( - output.status.success(), - "{}", - String::from_utf8_lossy(&output.stderr) - ); - assert_eq!(String::from_utf8(output.stdout).unwrap(), "ok\n"); - let path = - std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../compiler/examples/literal.endor"); - let output = Command::new(env!("CARGO_BIN_EXE_cli")) - .arg("eval") - .arg(path) - .output() - .unwrap(); - assert!(output.status.success()); - assert_eq!( - String::from_utf8(output.stdout).unwrap(), - "表达式 → 字节码 → VM\n" - ); -} - -#[test] -fn execution_errors_fail_without_success_output_and_report_their_stage() { - for (arguments, source, expected) in [ - ( - vec!["run", "-"], - "function main(): T {\n return missing\n}", - ":2:10: undefined name: missing", - ), - ( - vec!["run", "-"], - "function main(): T { return f() } function f(x: T): T { return x }", - "positional arguments", - ), - ( - vec!["run", "-"], - "function other(): T { return 1 }", - "entry function not found: main", - ), - ( - vec!["eval", "-"], - "1 + 2", - "arithmetic bytecode is not implemented", - ), - (vec!["eval", "-"], "1()", "does not support call"), - ( - vec!["run", "-"], - "function main(): T { return ) }", - "expected", - ), - ] { - let output = stdin_command(&arguments, source.as_bytes()); - assert!(!output.status.success()); - assert!(output.stdout.is_empty()); - let error = String::from_utf8(output.stderr).unwrap(); - assert!(error.contains(expected), "{error}"); - if expected == "positional arguments" { - assert!(error.contains("at main (:1, code unit")); - } - } - let output = stdin_command(&["eval", "-"], &[0xff]); - assert!(!output.status.success()); - assert!( - String::from_utf8(output.stderr) - .unwrap() - .contains("invalid UTF-8") - ); - for arguments in [ - vec!["run"], - vec!["run", "--entry"], - vec!["run", "--unknown", "-"], - vec!["eval"], - vec!["eval", "-", "extra"], - ] { - let output = Command::new(env!("CARGO_BIN_EXE_cli")) - .args(arguments) - .output() - .unwrap(); - assert!(!output.status.success()); - assert!(output.stdout.is_empty()); - assert!(String::from_utf8(output.stderr).unwrap().contains("Usage:")); - } -} diff --git a/crates/cli/tests/parse.rs b/crates/cli/tests/parse.rs deleted file mode 100644 index 783f387..0000000 --- a/crates/cli/tests/parse.rs +++ /dev/null @@ -1,139 +0,0 @@ -use std::io::Write; -use std::path::PathBuf; -use std::process::{Command, Stdio}; -use std::sync::atomic::{AtomicUsize, Ordering}; - -static NEXT_FILE: AtomicUsize = AtomicUsize::new(0); - -struct SourceFile(PathBuf); - -impl SourceFile { - fn new(content: &[u8]) -> Self { - let path = std::env::temp_dir().join(format!( - "aster-parse-{}-{}.endor", - std::process::id(), - NEXT_FILE.fetch_add(1, Ordering::Relaxed) - )); - std::fs::write(&path, content).unwrap(); - Self(path) - } -} - -impl Drop for SourceFile { - fn drop(&mut self) { - let _ = std::fs::remove_file(&self.0); - } -} - -#[test] -fn parse_command_prints_ast_without_executing_source() { - let source = SourceFile::new("function 表示(): String { return 未定義() }".as_bytes()); - let output = Command::new(env!("CARGO_BIN_EXE_cli")) - .arg("parse") - .arg(&source.0) - .output() - .unwrap(); - assert!( - output.status.success(), - "{}", - String::from_utf8_lossy(&output.stderr) - ); - let stdout = String::from_utf8(output.stdout).unwrap(); - assert!(stdout.contains("表示")); - assert!(stdout.contains("未定義")); - assert!(stdout.contains("Call")); -} - -#[test] -fn parse_command_reports_source_locations_and_rejects_invalid_utf8() { - let source = SourceFile::new(b"function f(): Number {\n return )\n}"); - let output = Command::new(env!("CARGO_BIN_EXE_cli")) - .arg("parse") - .arg(&source.0) - .output() - .unwrap(); - assert!(!output.status.success()); - assert!(output.stdout.is_empty()); - let stderr = String::from_utf8(output.stderr).unwrap(); - assert!(stderr.contains(":2:10:"), "{stderr}"); - assert!(stderr.contains("expected")); - let source = SourceFile::new(&[0xff]); - let output = Command::new(env!("CARGO_BIN_EXE_cli")) - .arg("parse") - .arg(&source.0) - .output() - .unwrap(); - assert!(!output.status.success()); - assert!(output.stdout.is_empty()); -} - -fn parse_stdin(source: &[u8]) -> std::process::Output { - let mut child = Command::new(env!("CARGO_BIN_EXE_cli")) - .args(["parse", "-"]) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let mut stdin = child.stdin.take().unwrap(); - stdin.write_all(source).unwrap(); - drop(stdin); - child.wait_with_output().unwrap() -} - -#[test] -fn parse_command_reads_stdin_to_eof_and_reports_its_origin() { - let output = parse_stdin("function 表示(): String { return \"日\" }".as_bytes()); - assert!( - output.status.success(), - "{}", - String::from_utf8_lossy(&output.stderr) - ); - assert!(String::from_utf8(output.stdout).unwrap().contains("表示")); - let output = parse_stdin(b"function f(): Number {\n return )\n}"); - assert!(!output.status.success()); - assert!( - String::from_utf8(output.stderr) - .unwrap() - .contains(":2:10:") - ); - let output = parse_stdin(&[0xff]); - assert!(!output.status.success()); - assert!( - String::from_utf8(output.stderr) - .unwrap() - .contains(": invalid UTF-8 at byte 0") - ); -} - -#[test] -fn parse_command_exports_versioned_json_for_expression_and_interactive_inputs() { - for (mode, source) in [("expression", "f(\"日\")"), ("interactive", "let x = 1")] { - let source = SourceFile::new(source.as_bytes()); - let output = Command::new(env!("CARGO_BIN_EXE_cli")) - .args(["parse", "--json", "--mode", mode]) - .arg(&source.0) - .output() - .unwrap(); - assert!( - output.status.success(), - "{}", - String::from_utf8_lossy(&output.stderr) - ); - let document = - frontend::ast::AstDocument::from_json(std::str::from_utf8(&output.stdout).unwrap()) - .unwrap(); - assert_eq!(document.schema, frontend::ast::AST_SCHEMA_ID); - match (mode, document.root) { - ("expression", frontend::ast::AstRoot::Expression(_)) - | ("interactive", frontend::ast::AstRoot::Interactive(_)) => {} - _ => panic!("incorrect parse mode"), - } - } - let output = Command::new(env!("CARGO_BIN_EXE_cli")) - .args(["parse", "--mode", "unknown", "-"]) - .output() - .unwrap(); - assert!(!output.status.success()); - assert!(output.stdout.is_empty()); -} diff --git a/crates/compiler/README.md b/crates/compiler/README.md index fc38943..a143062 100644 --- a/crates/compiler/README.md +++ b/crates/compiler/README.md @@ -6,7 +6,7 @@ | --- | --- | | [Cargo.toml](Cargo.toml) | compiler 包、本地 frontend/core 依赖与工作区 lint | | [src](src/README.md) | 分析、字节码生成与显式代码对象装载 | -| [examples](examples/README.md) | 可供 cli run/eval 的 Endor 源码文件,也是远端集成测试输入 | +| [examples](examples/README.md) | 可供 lyra run/eval 的 Endor 源码文件,也是远端集成测试输入 | | [tests](tests/README.md) | 源码到 VM、错误路径、名称与对象生命周期检查 | 公开入口为 analyze、compile_ast、compile_source、CompileOptions 和 CompiledCode::load。完整规则与所有权见 [编译链路](../../docs/runtime/compiler.md);CLI 使用见 [运行指南](../../docs/operations/README.md)。修改语法仍进入 [frontend](../frontend/README.md),修改指令/验证进入 [objects](../objects/README.md),修改执行进入 [core](../core/README.md)。返回 [工作区](../README.md)。 diff --git a/crates/compiler/examples/README.md b/crates/compiler/examples/README.md index 50fa90f..bb91784 100644 --- a/crates/compiler/examples/README.md +++ b/crates/compiler/examples/README.md @@ -12,13 +12,13 @@ | [literal.endor](literal.endor) | eval;返回“表达式 → 字节码 → VM” | ```sh -cli run crates/compiler/examples/hello.endor -cli run crates/compiler/examples/calls.endor -cli run crates/compiler/examples/shadowing.endor -cli run crates/compiler/examples/number.endor -cli eval crates/compiler/examples/literal.endor +lyra run crates/compiler/examples/hello.endor +lyra run crates/compiler/examples/calls.endor +lyra run crates/compiler/examples/shadowing.endor +lyra run crates/compiler/examples/number.endor +lyra eval crates/compiler/examples/literal.endor ``` run 默认调用无参数 main,可用 --entry name 选择另一个无参数入口;eval 解析一个完整表达式,二者都支持 - 读取 stdin 到 EOF。类型名称暂只保留语法,Callable 不要求预先注册语言类。例子不依赖 print 或其他默认标准库。编译器尚无算术运行语义;前端 basic.endor 含算术,只是解析示例,不能混作可执行示例。规则见 [编译契约](../../../docs/runtime/compiler.md),返回 [compiler](../README.md)。 -CI 使用 [endor_examples.py](../../../scripts/endor_examples.py) 再次执行真实 CLI 并保存实际结果,不只显示 Rust 测试名称。对应 Actions run 的 endor-execution-output- artifact 包含源码、命令、预期/实际 stdout、stderr、退出码和状态;位置与失败规则见 [验证报告](../../../docs/operations/validation.md)。新增 .endor 示例时同步 cases.json,未覆盖文件会让报告步骤失败。 +本目录与 cases.json 继续作为引擎测试资料。独立 Lyra CLI 的 CI 使用固定 Aster 子模块中的同一份样例,实际执行 lyra run/eval 并保存 stdout/stderr、退出码及报告 artifact;报告脚本和进程入口检查位于 Lyra 仓库。Aster CI 只验证引擎,不构建 CLI。 diff --git a/crates/compiler/tests/README.md b/crates/compiler/tests/README.md index 3e6a8eb..30990b7 100644 --- a/crates/compiler/tests/README.md +++ b/crates/compiler/tests/README.md @@ -2,4 +2,4 @@ [pipeline.rs](pipeline.rs) 将真实 Endor 示例交给 reader/parser/名称分析/代码生成/VM,检查函数值、前向引用、局部遮蔽、精确数字常量、语义诊断、宿主全局与属性回调、对象结果保活、跨 VM 模板装载、反复调用、错误帧清理、递归限额、共享交互命名空间及超过 255 的操作数。 -同时检查重复名称、局部提前读取、未声明名称、不支持的执行特性和不合法 AST 原文范围。CLI 的文件/stdin 与退出码检查见 [cli/tests](../../cli/tests/README.md)。源码说明验证意图;仅由 ces-control 的 cargo test --workspace --locked 执行,本地不运行,结果边界见 [验证](../../../docs/operations/validation.md)。返回 [compiler](../README.md)。 +同时检查重复名称、局部提前读取、未声明名称、不支持的执行特性和不合法 AST 原文范围。CLI 的文件/stdin 与退出码检查见 [cli/tests](https://gitea.entroia.com/Zayden-Jung/endorphin-lyra-cli/src/branch/main/crates/cli/tests/README.md)。源码说明验证意图;仅由 ces-control 的 cargo test --workspace --locked 执行,本地不运行,结果边界见 [验证](../../../docs/operations/validation.md)。返回 [compiler](../README.md)。 diff --git a/crates/frontend/README.md b/crates/frontend/README.md index 30f8709..3fbff65 100644 --- a/crates/frontend/README.md +++ b/crates/frontend/README.md @@ -9,6 +9,6 @@ | [editor](editor/README.md) | 同源生成的 TypeScript 类型/遍历器和 JSON Schema | | [src](src/README.md) | 阅读器、SourceUnit、token、源码范围、诊断、AST、词法规则独立在 [lexer.peg](src/lexer.peg),语法规则在 [grammar.peg](src/grammar.peg) | | [tests](tests/README.md) | Unicode、优先级、函数、完整输入与错误位置验证 | -| [examples](examples/README.md) | 可交给 cli parse 的源码样例 | +| [examples](examples/README.md) | 可交给 lyra parse 的源码样例 | -宿主通过 EndorReader 读取字符串、流、文件或注入的 URI provider,取得 SourceUnit 后调用 lex_source、parse_source、parse_source_expression;统一入口 parse_ast/parse_source_ast 通过 ParseMode 返回 AstRoot;低层 lex、parse、parse_expression 仍保留。AST 定义、生成命令、Visitor 与 JSON 交换见 [AST 模型](../../docs/runtime/ast.md)。读取契约见 [Endor 阅读器](../../docs/runtime/reader.md)。Objects 保持零依赖,Core 仅依赖本地 Objects;后续把 AST 转成已有字节码并交给 VM 解释执行;这是平台内部准备步骤,尚未接通。资源修订、依赖锁和解释器统一升级的设计方向见 [执行与版本模型](../../docs/architecture/execution-and-versions.md)。返回 [工作区](../README.md)。 +宿主通过 EndorReader 读取字符串、流、文件或注入的 URI provider,取得 SourceUnit 后调用 lex_source、parse_source、parse_source_expression;统一入口 parse_ast/parse_source_ast 通过 ParseMode 返回 AstRoot;低层 lex、parse、parse_expression 仍保留。AST 定义、生成命令、Visitor 与 JSON 交换见 [AST 模型](../../docs/runtime/ast.md)。读取契约见 [Endor 阅读器](../../docs/runtime/reader.md)。Objects 保持零依赖,Core 仅依赖本地 Objects;独立 Compiler 已将 AST 转成 wordcode 并交给 VM 执行;这是平台内部准备步骤。资源修订、依赖锁和解释器统一升级的设计方向见 [执行与版本模型](../../docs/architecture/execution-and-versions.md)。返回 [工作区](../README.md)。 diff --git a/crates/frontend/examples/README.md b/crates/frontend/examples/README.md index 38dce4a..83ea471 100644 --- a/crates/frontend/examples/README.md +++ b/crates/frontend/examples/README.md @@ -2,4 +2,4 @@ [basic.endor](basic.endor) 包含字符串函数、位置参数、let 绑定、return 与算术表达式。它展示当前能解析的源码,不表示已能编译和执行运算。 -在指定远端环境使用 `cli parse crates/frontend/examples/basic.endor` 输出 AST;工作区运行方法见 [运行指南](../../../docs/operations/README.md)。syntax.rs 包含此样例的解析检查。返回 [frontend](../README.md)。 +在指定远端环境使用 `lyra parse crates/frontend/examples/basic.endor` 输出 AST;工作区运行方法见 [运行指南](../../../docs/operations/README.md)。syntax.rs 包含此样例的解析检查。返回 [frontend](../README.md)。 diff --git a/crates/host/Cargo.toml b/crates/host/Cargo.toml deleted file mode 100644 index db6bc5a..0000000 --- a/crates/host/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "host" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true - -[dependencies] -serde.workspace = true -serde_json.workspace = true - -[dev-dependencies] -tempfile = "3" - -[lints] -workspace = true diff --git a/crates/host/README.md b/crates/host/README.md deleted file mode 100644 index 69e8a24..0000000 --- a/crates/host/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# host - -宿主基础信息的文件层,与 Core 和数据库 Storage 分开。CLI/Server 使用它保存 host.version、deployment.release 和其他命名信息;不打开数据库,不保存 VM 堆。 - -| 内容 | 职责 | -| --- | --- | -| Cargo.toml | host 包、Serde/JSON 依赖及远端检查依赖 | -| [src](src/README.md) | MetadataStore、UTF-8 JSON 文件与迁移导入 | -| [tests](tests/README.md) | 持久读取、替换、并发、坏记录、路径边界和幂等导入 | - -ASTER_STATE_DIR 指定信息目录;默认 data/host,容器使用 /data/host。协议、生命周期及旧数据库一次性迁移见 [宿主信息](../../docs/operations/host-information.md)。返回 [工作区](../README.md)。 diff --git a/crates/host/src/README.md b/crates/host/src/README.md deleted file mode 100644 index 4fb6fd8..0000000 --- a/crates/host/src/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Host 源码导航 - -| 文件 | 职责 | -| --- | --- | -| [lib.rs](lib.rs) | 导出 MetadataStore,明确文件层不依赖 VM 或 SQL | -| [metadata.rs](metadata.rs) | 目录配置、键校验、逐键 JSON 读取/同步写入、原子发布、仅导入缺失项 | - -文件数据以 UTF-8 JSON 保存,操作系统路径用 PathBuf;坏记录不自动覆盖。每个键独立发布,不提供多键事务。旧库导入不覆盖新文件,规则见 [宿主信息](../../../docs/operations/host-information.md)。返回 [host](../README.md)。 diff --git a/crates/host/src/lib.rs b/crates/host/src/lib.rs deleted file mode 100644 index 2758209..0000000 --- a/crates/host/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! Host information stored in UTF-8 JSON files, independently of the VM and SQL. -mod metadata; - -pub use metadata::MetadataStore; diff --git a/crates/host/src/metadata.rs b/crates/host/src/metadata.rs deleted file mode 100644 index e5c6ca5..0000000 --- a/crates/host/src/metadata.rs +++ /dev/null @@ -1,172 +0,0 @@ -use std::fs::{self, OpenOptions}; -use std::io::{self, Read, Write}; -use std::path::{Path, PathBuf}; -use std::sync::atomic::{AtomicU64, Ordering}; - -use serde::{Deserialize, Serialize}; - -static NEXT_TEMP: AtomicU64 = AtomicU64::new(0); -const FORMAT_VERSION: u32 = 1; - -#[derive(Deserialize)] -struct LegacyRecord { - key: String, - value: String, -} - -#[derive(Serialize, Deserialize)] -struct Record { - format_version: u32, - key: String, - value: String, -} - -/// One independently replaceable JSON file per ASCII metadata key. Clones -/// share a directory, not a cached map; reads always observe the file system. -#[derive(Debug, Clone)] -pub struct MetadataStore { - directory: PathBuf, -} - -impl MetadataStore { - /// Does not create the directory until the first write. - pub fn new(directory: impl Into) -> io::Result { - let directory = directory.into(); - if directory.as_os_str().is_empty() { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "metadata directory is empty", - )); - } - Ok(Self { directory }) - } - - /// Preserve OS paths without requiring the environment value to be UTF-8. - pub fn from_env() -> io::Result { - Self::new( - std::env::var_os("ASTER_STATE_DIR") - .map(PathBuf::from) - .unwrap_or_else(|| PathBuf::from("data/host")), - ) - } - - pub fn directory(&self) -> &Path { - &self.directory - } - - pub fn get(&self, key: &str) -> io::Result> { - let path = self.path(key)?; - let bytes = match fs::read(path) { - Ok(bytes) => bytes, - Err(error) if error.kind() == io::ErrorKind::NotFound => return Ok(None), - Err(error) => return Err(error), - }; - let record: Record = serde_json::from_slice(&bytes) - .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?; - if record.format_version != FORMAT_VERSION || record.key != key { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "metadata key or format version mismatch", - )); - } - Ok(Some(record.value)) - } - - /// Writes a new sibling file, syncs it, then publishes it with rename. - /// Different keys do not share a read/modify/write map. For the same key, - /// the last successful rename wins. This is not a multi-key transaction. - pub fn put(&self, key: &str, value: &str) -> io::Result<()> { - self.store(key, value, true).map(|_| ()) - } - - /// One-time migration input: a JSON array of key/value string records. - /// Validate all keys first, and never overwrite an existing file record. - pub fn import_missing(&self, reader: impl Read) -> io::Result { - let records: Vec = serde_json::from_reader(reader) - .map_err(|error| io::Error::new(io::ErrorKind::InvalidData, error))?; - for record in &records { - self.path(&record.key)?; - } - let mut imported = 0; - for record in records { - if self.store(&record.key, &record.value, false)? { - imported += 1; - } else if self.get(&record.key)?.is_none() { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "existing metadata disappeared during import", - )); - } - } - Ok(imported) - } - - fn store(&self, key: &str, value: &str, replace: bool) -> io::Result { - let destination = self.path(key)?; - let mut contents = serde_json::to_vec_pretty(&Record { - format_version: FORMAT_VERSION, - key: key.into(), - value: value.into(), - }) - .map_err(io::Error::other)?; - contents.push(b'\n'); - fs::create_dir_all(&self.directory)?; - for _ in 0..32 { - let sequence = NEXT_TEMP.fetch_add(1, Ordering::Relaxed); - let temporary = self - .directory - .join(format!(".{key}.{}.{sequence}.tmp", std::process::id())); - let mut options = OpenOptions::new(); - options.write(true).create_new(true); - #[cfg(unix)] - { - use std::os::unix::fs::OpenOptionsExt; - options.mode(0o600); - } - let mut file = match options.open(&temporary) { - Ok(file) => file, - Err(error) if error.kind() == io::ErrorKind::AlreadyExists => continue, - Err(error) => return Err(error), - }; - let result = (|| { - file.write_all(&contents)?; - file.sync_all()?; - drop(file); - if replace { - fs::rename(&temporary, &destination)?; - Ok(true) - } else { - // Atomic create-if-absent: a concurrent writer's record - // cannot be replaced by the legacy import. - match fs::hard_link(&temporary, &destination) { - Ok(()) => Ok(true), - Err(error) if error.kind() == io::ErrorKind::AlreadyExists => Ok(false), - Err(error) => Err(error), - } - } - })(); - let _ = fs::remove_file(&temporary); - return result; - } - Err(io::Error::new( - io::ErrorKind::AlreadyExists, - "cannot reserve a metadata temporary file", - )) - } - - fn path(&self, key: &str) -> io::Result { - if key.is_empty() - || key.len() > 128 - || !key.as_bytes()[0].is_ascii_alphanumeric() - || !key - .bytes() - .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-')) - { - return Err(io::Error::new( - io::ErrorKind::InvalidInput, - "metadata keys must start with an ASCII letter/digit and contain at most 128 ASCII letters, digits, dots, underscores or hyphens", - )); - } - Ok(self.directory.join(format!("{key}.json"))) - } -} diff --git a/crates/host/tests/README.md b/crates/host/tests/README.md deleted file mode 100644 index bbb8a68..0000000 --- a/crates/host/tests/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Host 文件信息检查 - -[metadata.rs](metadata.rs) 检查缺失读取不建目录、日文/中文/emoji 值和日文路径、重开与同键替换、非法键不越界、坏 JSON/版本/键不匹配不改原文件、并发替换可见完整记录,以及旧记录幂等导入和保留更新值。 - -检查使用临时目录,仅在指定远端环境运行。本地不执行文件行为测试;当前是否通过应以对应提交的 CI 为准。命令见 [验证](../../../docs/operations/validation.md),返回 [host](../README.md)。 diff --git a/crates/host/tests/metadata.rs b/crates/host/tests/metadata.rs deleted file mode 100644 index 30a3be8..0000000 --- a/crates/host/tests/metadata.rs +++ /dev/null @@ -1,131 +0,0 @@ -use host::MetadataStore; - -#[test] -fn missing_reads_do_not_create_state() { - let temporary = tempfile::tempdir().unwrap(); - let directory = temporary.path().join("state"); - let store = MetadataStore::new(&directory).unwrap(); - assert_eq!(store.get("missing").unwrap(), None); - assert!(!directory.exists()); -} - -#[test] -fn utf8_values_survive_reopening_and_same_key_replacement() { - let temporary = tempfile::tempdir().unwrap(); - let directory = temporary.path().join("日本語"); - let store = MetadataStore::new(&directory).unwrap(); - store.put("host.version", "0.1.0").unwrap(); - store.put("project.note", "日本語・中文・😀").unwrap(); - let reopened = MetadataStore::new(&directory).unwrap(); - assert_eq!( - reopened.get("project.note").unwrap().as_deref(), - Some("日本語・中文・😀") - ); - reopened.put("host.version", "0.2.0").unwrap(); - assert_eq!(store.get("host.version").unwrap().as_deref(), Some("0.2.0")); - assert_eq!( - store.get("project.note").unwrap().as_deref(), - Some("日本語・中文・😀") - ); - let record: serde_json::Value = - serde_json::from_slice(&std::fs::read(directory.join("project.note.json")).unwrap()) - .unwrap(); - assert_eq!(record["format_version"], 1); - assert_eq!(record["key"], "project.note"); -} - -#[test] -fn keys_cannot_escape_the_state_directory() { - let temporary = tempfile::tempdir().unwrap(); - let directory = temporary.path().join("state"); - let store = MetadataStore::new(&directory).unwrap(); - for key in [ - "", - "../outside", - "/outside", - "nested/key", - "nested\\key", - ".hidden", - "日本語", - ] { - assert!(store.put(key, "invalid").is_err(), "{key}"); - assert!(store.get(key).is_err(), "{key}"); - } - assert!(store.put(&"x".repeat(129), "invalid").is_err()); - assert!(!directory.exists()); -} - -#[test] -fn corrupt_or_mismatched_records_fail_without_being_rewritten() { - let temporary = tempfile::tempdir().unwrap(); - let store = MetadataStore::new(temporary.path()).unwrap(); - let path = temporary.path().join("host.version.json"); - for bytes in [ - b"not-json".as_slice(), - b"\xff".as_slice(), - br#"{"format_version":2,"key":"host.version","value":"old"}"#.as_slice(), - br#"{"format_version":1,"key":"wrong","value":"old"}"#.as_slice(), - ] { - std::fs::write(&path, bytes).unwrap(); - assert!(store.get("host.version").is_err()); - assert_eq!(std::fs::read(&path).unwrap(), bytes); - } -} - -#[test] -fn concurrent_replacements_expose_complete_records_and_preserve_other_keys() { - let temporary = tempfile::tempdir().unwrap(); - let store = MetadataStore::new(temporary.path()).unwrap(); - store.put("independent", "keep").unwrap(); - let a = "a".repeat(8192); - let b = "b".repeat(8192); - store.put("shared", &a).unwrap(); - std::thread::scope(|scope| { - for value in [&a, &b] { - let store = &store; - scope.spawn(move || { - for _ in 0..8 { - store.put("shared", value).unwrap(); - } - }); - } - for _ in 0..64 { - let value = store.get("shared").unwrap().unwrap(); - assert!(value == a || value == b); - } - }); - assert_eq!(store.get("independent").unwrap().as_deref(), Some("keep")); - assert!(std::fs::read_dir(temporary.path()).unwrap().all(|entry| { - !entry - .unwrap() - .file_name() - .to_string_lossy() - .ends_with(".tmp") - })); -} - -#[test] -fn legacy_import_is_repeatable_and_preserves_newer_file_values() { - let temporary = tempfile::tempdir().unwrap(); - let store = MetadataStore::new(temporary.path()).unwrap(); - store.put("host.version", "new").unwrap(); - let input = - br#"[{"key":"host.version","value":"old"},{"key":"project.note","value":"legacy"}]"#; - assert_eq!(store.import_missing(input.as_slice()).unwrap(), 1); - assert_eq!(store.import_missing(input.as_slice()).unwrap(), 0); - assert_eq!(store.get("host.version").unwrap().as_deref(), Some("new")); - assert_eq!( - store.get("project.note").unwrap().as_deref(), - Some("legacy") - ); -} - -#[test] -fn invalid_import_keys_are_rejected_before_any_record_is_written() { - let temporary = tempfile::tempdir().unwrap(); - let directory = temporary.path().join("state"); - let store = MetadataStore::new(&directory).unwrap(); - let input = br#"[{"key":"valid","value":"keep"},{"key":"../outside","value":"invalid"}]"#; - assert!(store.import_missing(input.as_slice()).is_err()); - assert!(!directory.exists()); -} diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml deleted file mode 100644 index 749cb40..0000000 --- a/crates/server/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "server" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true - -[dependencies] -core.workspace = true -host.workspace = true -axum.workspace = true -serde.workspace = true -tokio.workspace = true - -[lints] -workspace = true diff --git a/crates/server/README.md b/crates/server/README.md deleted file mode 100644 index 4cab163..0000000 --- a/crates/server/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# server - -最小常驻 HTTP 宿主,提供存活检查和只读 VM 信息。通过 Arc 共享 Vm,启动不读取数据库配置、不连接或写入数据库;通过 host 包把 host.version 写入文件。特殊类的数据库能力尚未接入 HTTP 宿主。 - -| 内容 | 职责 | -| --- | --- | -| Cargo.toml | core/host、Axum、Tokio、Serde 依赖;无 storage 依赖 | -| [src](src/README.md) | AppState、启动、路由、信号与退出 | - -HTTP 返回内容见 [运行指南](../../docs/operations/README.md),监听变量见 [配置](../../docs/operations/configuration.md),预置 PostgreSQL 资源见 [部署](../../docs/operations/deployment.md)。没有语言执行接口或可变 VM 请求上下文。返回 [工作区](../README.md)。 diff --git a/crates/server/src/README.md b/crates/server/src/README.md deleted file mode 100644 index 0e1fe20..0000000 --- a/crates/server/src/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Server 入口 - -[main.rs](main.rs) 包含所有宿主启动代码: - -| 部分 | 职责 | -| --- | --- | -| AppState | 共享只读 Arc | -| main/run | 创建空 VM、绑定地址、设置退出信号、写文件 host.version、组装路由;不创建数据库连接 | -| health | 返回 200 和 status=ok,仅检查 HTTP 宿主可响应 | -| bootstrap | 返回对象模型、type_system=false 和堆对象数量,不分配对象 | -| shutdown_signal | Ctrl-C;Unix 上同时接受 SIGTERM,触发 Axum 优雅退出 | - -/health 不表示 PostgreSQL、特殊类查询或执行脚本正常;数据库检查应属于实际消费连接的对象。宿主独立于数据库的行为由远端 smoke.py 验证,见 [验证](../../../docs/operations/validation.md)。改变端点同步 [运行指南](../../../docs/operations/README.md) 和 [scripts](../../../scripts/README.md)。返回 [server](../README.md)。 diff --git a/crates/server/src/main.rs b/crates/server/src/main.rs deleted file mode 100644 index 76d0612..0000000 --- a/crates/server/src/main.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::error::Error; -use std::future::Future; -use std::process::ExitCode; -use std::sync::Arc; - -use axum::{Json, Router, extract::State, routing::get}; -use endor_core::Vm; -use host::MetadataStore; -use serde::Serialize; - -#[derive(Clone)] -struct AppState { - vm: Arc, -} - -#[derive(Serialize)] -struct Health { - status: &'static str, -} - -#[derive(Serialize)] -struct BootstrapInfo { - object_model: &'static str, - type_system: bool, - heap_objects: usize, -} - -#[tokio::main] -async fn main() -> ExitCode { - match run().await { - Ok(()) => ExitCode::SUCCESS, - Err(error) => { - eprintln!("server: {error}"); - ExitCode::FAILURE - } - } -} - -async fn run() -> Result<(), Box> { - let vm = Arc::new(Vm::new()?); - let address = std::env::var("ASTER_LISTEN").unwrap_or_else(|_| "127.0.0.1:8080".into()); - let listener = tokio::net::TcpListener::bind(&address).await?; - let shutdown = shutdown_signal()?; - MetadataStore::from_env()?.put("host.version", env!("CARGO_PKG_VERSION"))?; - let app = Router::new() - .route("/health", get(health)) - .route("/bootstrap", get(bootstrap)) - .with_state(AppState { vm }); - println!("Endor host listening on {}", listener.local_addr()?); - axum::serve(listener, app) - .with_graceful_shutdown(shutdown) - .await?; - Ok(()) -} - -async fn health() -> Json { - Json(Health { status: "ok" }) -} - -async fn bootstrap(State(state): State) -> Json { - Json(BootstrapInfo { - object_model: "EndorObject", - type_system: false, - heap_objects: state.vm.object_count(), - }) -} - -fn shutdown_signal() -> std::io::Result> { - #[cfg(unix)] - let mut terminate = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())?; - Ok(async move { - #[cfg(unix)] - tokio::select! { _ = tokio::signal::ctrl_c() => {}, _ = terminate.recv() => {} } - #[cfg(not(unix))] - let _ = tokio::signal::ctrl_c().await; - }) -} diff --git a/crates/storage/Cargo.toml b/crates/storage/Cargo.toml deleted file mode 100644 index d9de0b9..0000000 --- a/crates/storage/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "storage" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true - -[dependencies] -sqlx.workspace = true -tokio.workspace = true - -[dev-dependencies] -tempfile = "3" - -[lints] -workspace = true diff --git a/crates/storage/README.md b/crates/storage/README.md deleted file mode 100644 index 951b2e5..0000000 --- a/crates/storage/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# storage - -按需数据库连接适配层,供将来需要数据库的特殊类或宿主适配器使用。依赖 SQLx/Tokio,不依赖 VM 内核;普通对象、CLI 和 Server 不连接这层。 - -| 内容 | 入口 | -| --- | --- | -| Cargo.toml | SQLx、Tokio 与远端检查依赖 | -| [src](src/README.md) | 显式 DatabaseConfig、连接池与关闭 | -| [tests](tests/README.md) | 连接不建表、调用方 SQL、隔离/持久化、真实 PostgreSQL | - -没有默认数据库、自动环境变量读取、基础信息表或通用元数据 put/get。特殊类的名称、布局与查询接口尚未定义;当前只保留连接能力。设计边界见 [数据库对象](../../docs/runtime/database-objects.md),参数见 [配置](../../docs/operations/configuration.md)。返回 [工作区](../README.md)。 diff --git a/crates/storage/src/README.md b/crates/storage/src/README.md deleted file mode 100644 index 124bb60..0000000 --- a/crates/storage/src/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# 数据库连接源码导航 - -[lib.rs](lib.rs) 是唯一实现文件:DatabaseConfig 显式选择 SQLite 文件/内存或 PostgreSQL;DatabaseConnection 提供 connect、sqlite_pool/postgres_pool、backend_name、ping 和 close;DatabaseError 对公开错误脱敏。 - -connect 只建立连接池,不执行应用 DDL,不保存基础信息。没有 schema.sql、初始化表、put/get 或 from_env。业务表和查询由未来的数据库特殊类决定;本层不替它定义数据模型。 - -克隆共享连接池,close 会关闭所有克隆;具体生命周期及限制见 [数据库对象](../../../docs/runtime/database-objects.md)。配置变动同步 [配置](../../../docs/operations/configuration.md) 和 [tests](../tests/README.md)。返回 [storage](../README.md)。 diff --git a/crates/storage/src/lib.rs b/crates/storage/src/lib.rs deleted file mode 100644 index 774040c..0000000 --- a/crates/storage/src/lib.rs +++ /dev/null @@ -1,181 +0,0 @@ -//! Explicit database connections for database-backed objects. -//! Connecting never creates application tables or stores VM/host metadata. -use std::fmt; -use std::path::PathBuf; -use std::str::FromStr; -use std::time::Duration; - -use sqlx::postgres::{PgConnectOptions, PgPoolOptions}; -use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions}; -use sqlx::{PgPool, SqlitePool}; - -/// An explicit PostgreSQL configuration never falls back to SQLite. -#[derive(Clone)] -pub enum DatabaseConfig { - SqliteFile(PathBuf), - SqliteMemory, - Postgres(String), -} - -impl fmt::Debug for DatabaseConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::SqliteFile(path) => f.debug_tuple("SqliteFile").field(path).finish(), - Self::SqliteMemory => f.write_str("SqliteMemory"), - Self::Postgres(_) => f.write_str("Postgres([REDACTED])"), - } - } -} - -#[derive(Debug)] -pub enum DatabaseError { - Config(&'static str), - Directory(std::io::Error), - Database(sqlx::Error), -} - -impl fmt::Display for DatabaseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Config(message) => f.write_str(message), - Self::Directory(_) => f.write_str("cannot create SQLite parent directory"), - // CLI/HTTP logs must never print a connection URL or credentials. - Self::Database(_) => { - f.write_str("database operation failed; no fallback was attempted") - } - } - } -} - -impl std::error::Error for DatabaseError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::Config(_) => None, - Self::Directory(error) => Some(error), - Self::Database(error) => Some(error), - } - } -} - -impl From for DatabaseError { - fn from(error: sqlx::Error) -> Self { - Self::Database(error) - } -} - -#[derive(Clone)] -enum Backend { - Sqlite(SqlitePool), - Postgres(PgPool), -} - -/// Created only on explicit request by an object or host adapter. Clones share -/// a pool; close() closes it for all users. There is no automatic schema or data. -/// In-memory SQLite uses one connection to preserve its database lifetime. -#[derive(Clone)] -pub struct DatabaseConnection { - backend: Backend, -} - -impl DatabaseConnection { - /// Connects eagerly to the explicit backend without executing application DDL. - pub async fn connect(config: &DatabaseConfig) -> Result { - let backend = match config { - DatabaseConfig::Postgres(url) => { - if !(url.starts_with("postgres://") || url.starts_with("postgresql://")) { - return Err(DatabaseError::Config( - "expected a PostgreSQL connection URL", - )); - } - let options = PgConnectOptions::from_str(url) - .map_err(|_| DatabaseError::Config("invalid PostgreSQL connection URL"))?; - Backend::Postgres( - PgPoolOptions::new() - .max_connections(5) - .acquire_timeout(Duration::from_secs(5)) - .connect_with(options) - .await?, - ) - } - DatabaseConfig::SqliteFile(path) => { - if path.as_os_str().is_empty() || path == std::path::Path::new(":memory:") { - return Err(DatabaseError::Config( - "expected a nonempty file path; use SqliteMemory for :memory:", - )); - } - if let Some(parent) = path.parent().filter(|p| !p.as_os_str().is_empty()) { - tokio::fs::create_dir_all(parent) - .await - .map_err(DatabaseError::Directory)?; - } - let options = SqliteConnectOptions::new() - .filename(path) - .create_if_missing(true) - .journal_mode(SqliteJournalMode::Wal) - .busy_timeout(Duration::from_secs(5)); - Backend::Sqlite( - SqlitePoolOptions::new() - .max_connections(5) - .acquire_timeout(Duration::from_secs(5)) - .connect_with(options) - .await?, - ) - } - DatabaseConfig::SqliteMemory => { - let options = SqliteConnectOptions::new().in_memory(true); - Backend::Sqlite( - SqlitePoolOptions::new() - .max_connections(1) - .min_connections(1) - .idle_timeout(None) - .max_lifetime(None) - .connect_with(options) - .await?, - ) - } - }; - Ok(Self { backend }) - } - - /// Backend-specific access for the database-backed object implementation. - pub fn sqlite_pool(&self) -> Option<&SqlitePool> { - match &self.backend { - Backend::Sqlite(pool) => Some(pool), - Backend::Postgres(_) => None, - } - } - - pub fn postgres_pool(&self) -> Option<&PgPool> { - match &self.backend { - Backend::Postgres(pool) => Some(pool), - Backend::Sqlite(_) => None, - } - } - - pub fn backend_name(&self) -> &'static str { - match &self.backend { - Backend::Sqlite(_) => "sqlite", - Backend::Postgres(_) => "postgresql", - } - } - - pub async fn ping(&self) -> Result<(), DatabaseError> { - match &self.backend { - Backend::Sqlite(pool) => { - sqlx::query("SELECT 1").execute(pool).await?; - } - Backend::Postgres(pool) => { - sqlx::query("SELECT 1").execute(pool).await?; - } - } - Ok(()) - } - - /// Explicitly closes the shared pool, including all DatabaseConnection clones. - pub async fn close(&self) { - match &self.backend { - Backend::Sqlite(pool) => pool.close().await, - Backend::Postgres(pool) => pool.close().await, - } - } -} diff --git a/crates/storage/tests/README.md b/crates/storage/tests/README.md deleted file mode 100644 index 66e866f..0000000 --- a/crates/storage/tests/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# 数据库连接检查 - -| 文件 | 覆盖与前提 | -| --- | --- | -| [storage.rs](storage.rs) | 显式错误配置、凭据脱敏、SQLite 连接后无应用表、克隆共享与独立内存隔离、调用方自建数据跨文件重连、目录错误、PostgreSQL 失败不降级 | -| [postgres.rs](postgres.rs) | 真实连接后 public 无表;测试在事务中创建临时 class_rows、参数绑定和读取,提交自动删除临时表,再连接;默认 ignore,需全新专用 ASTER_TEST_POSTGRES_URL | - -所有业务表都由测试自己创建,连接层不得自动初始化基础信息表。临时 SQLite 文件和 PostgreSQL 测试库由远端 CI 隔离,不使用生产数据。检查命令见 [验证](../../../docs/operations/validation.md),原生连接能力边界见 [数据库对象](../../../docs/runtime/database-objects.md)。本地不运行测试或下载依赖。返回 [storage](../README.md)。 diff --git a/crates/storage/tests/postgres.rs b/crates/storage/tests/postgres.rs deleted file mode 100644 index 65135aa..0000000 --- a/crates/storage/tests/postgres.rs +++ /dev/null @@ -1,53 +0,0 @@ -use storage::{DatabaseConfig, DatabaseConnection}; - -/// Run against a fresh disposable database, never a production or shared schema. -#[tokio::test] -#[ignore = "requires ASTER_TEST_POSTGRES_URL pointing to a fresh disposable PostgreSQL database"] -async fn postgres_connects_without_schema_and_executes_caller_owned_sql() { - let url = std::env::var("ASTER_TEST_POSTGRES_URL") - .expect("set ASTER_TEST_POSTGRES_URL to a fresh disposable PostgreSQL database"); - let config = DatabaseConfig::Postgres(url); - let database = DatabaseConnection::connect(&config).await.unwrap(); - assert_eq!(database.backend_name(), "postgresql"); - assert!(database.sqlite_pool().is_none()); - database.ping().await.unwrap(); - let pool = database.postgres_pool().unwrap(); - let tables: i64 = sqlx::query_scalar( - "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public'", - ) - .fetch_one(pool) - .await - .unwrap(); - assert_eq!(tables, 0, "connecting must not create application tables"); - let mut transaction = pool.begin().await.unwrap(); - sqlx::query( - "CREATE TEMP TABLE class_rows (id INTEGER PRIMARY KEY, value TEXT NOT NULL) ON COMMIT DROP", - ) - .execute(&mut *transaction) - .await - .unwrap(); - sqlx::query("INSERT INTO class_rows (id, value) VALUES ($1, $2)") - .bind(1_i64) - .bind("你好 ' ; $1") - .execute(&mut *transaction) - .await - .unwrap(); - let value: String = sqlx::query_scalar("SELECT value FROM class_rows WHERE id = $1") - .bind(1_i64) - .fetch_one(&mut *transaction) - .await - .unwrap(); - assert_eq!(value, "你好 ' ; $1"); - transaction.commit().await.unwrap(); - database.close().await; - let reopened = DatabaseConnection::connect(&config).await.unwrap(); - reopened.ping().await.unwrap(); - let tables: i64 = sqlx::query_scalar( - "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public'", - ) - .fetch_one(reopened.postgres_pool().unwrap()) - .await - .unwrap(); - assert_eq!(tables, 0); - reopened.close().await; -} diff --git a/crates/storage/tests/storage.rs b/crates/storage/tests/storage.rs deleted file mode 100644 index 151e091..0000000 --- a/crates/storage/tests/storage.rs +++ /dev/null @@ -1,130 +0,0 @@ -use storage::{DatabaseConfig, DatabaseConnection}; - -#[test] -fn postgres_configuration_debug_redacts_credentials() { - let config = DatabaseConfig::Postgres("postgres://user:secret@localhost/db".into()); - assert!(!format!("{config:?}").contains("secret")); -} - -#[tokio::test] -async fn explicit_invalid_backends_fail_without_fallback() { - for config in [ - DatabaseConfig::Postgres(String::new()), - DatabaseConfig::Postgres("mysql://localhost/db".into()), - DatabaseConfig::SqliteFile("".into()), - DatabaseConfig::SqliteFile(":memory:".into()), - ] { - assert!(DatabaseConnection::connect(&config).await.is_err()); - } -} - -#[tokio::test] -async fn memory_connections_start_without_tables_and_clones_share_the_pool() { - let database = DatabaseConnection::connect(&DatabaseConfig::SqliteMemory) - .await - .unwrap(); - let other = DatabaseConnection::connect(&DatabaseConfig::SqliteMemory) - .await - .unwrap(); - assert!(database.postgres_pool().is_none()); - let pool = database.sqlite_pool().unwrap(); - let tables: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM sqlite_master WHERE type = 'table'") - .fetch_one(pool) - .await - .unwrap(); - assert_eq!(tables, 0, "connecting must not create application tables"); - // This schema belongs to the test's simulated database-backed object. - sqlx::query("CREATE TABLE class_rows (id INTEGER PRIMARY KEY, value TEXT NOT NULL)") - .execute(pool) - .await - .unwrap(); - sqlx::query("INSERT INTO class_rows (id, value) VALUES (?, ?)") - .bind(1_i64) - .bind("你好 ' ? $1 ;") - .execute(pool) - .await - .unwrap(); - let cloned = database.clone(); - let value: String = sqlx::query_scalar("SELECT value FROM class_rows WHERE id = ?") - .bind(1_i64) - .fetch_one(cloned.sqlite_pool().unwrap()) - .await - .unwrap(); - assert_eq!(value, "你好 ' ? $1 ;"); - let other_tables: i64 = - sqlx::query_scalar("SELECT COUNT(*) FROM sqlite_master WHERE type = 'table'") - .fetch_one(other.sqlite_pool().unwrap()) - .await - .unwrap(); - assert_eq!(other_tables, 0); - database.close().await; - assert!(cloned.ping().await.is_err()); - other.close().await; -} - -#[tokio::test] -async fn explicit_sqlite_file_preserves_only_caller_created_data_across_reconnects() { - let dir = tempfile::tempdir().unwrap(); - let path = dir.path().join("nested/data/class.db"); - let config = DatabaseConfig::SqliteFile(path.clone()); - let database = DatabaseConnection::connect(&config).await.unwrap(); - let pool = database.sqlite_pool().unwrap(); - let tables: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM sqlite_master WHERE type = 'table'") - .fetch_one(pool) - .await - .unwrap(); - assert_eq!(tables, 0); - sqlx::query("CREATE TABLE class_rows (id INTEGER PRIMARY KEY, value TEXT NOT NULL)") - .execute(pool) - .await - .unwrap(); - sqlx::query("INSERT INTO class_rows (id, value) VALUES (?, ?)") - .bind(1_i64) - .bind("caller-owned data") - .execute(pool) - .await - .unwrap(); - database.close().await; - assert!(path.is_file()); - let reopened = DatabaseConnection::connect(&config).await.unwrap(); - let value: String = sqlx::query_scalar("SELECT value FROM class_rows WHERE id = ?") - .bind(1_i64) - .fetch_one(reopened.sqlite_pool().unwrap()) - .await - .unwrap(); - assert_eq!(value, "caller-owned data"); - let tables: Vec = - sqlx::query_scalar("SELECT name FROM sqlite_master WHERE type = 'table'") - .fetch_all(reopened.sqlite_pool().unwrap()) - .await - .unwrap(); - assert_eq!(tables, vec!["class_rows"]); - reopened.close().await; -} - -#[tokio::test] -async fn invalid_sqlite_directory_reports_error() { - let dir = tempfile::tempdir().unwrap(); - let file = dir.path().join("file"); - std::fs::write(&file, "occupied").unwrap(); - assert!( - DatabaseConnection::connect(&DatabaseConfig::SqliteFile(file.join("class.db"))) - .await - .is_err() - ); -} - -#[tokio::test] -async fn postgres_connection_failure_is_an_error() { - let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap(); - let url = format!( - "postgres://aster:secret@{}/aster?sslmode=disable", - listener.local_addr().unwrap() - ); - let error = match DatabaseConnection::connect(&DatabaseConfig::Postgres(url)).await { - Ok(_) => panic!("unreachable PostgreSQL must fail"), - Err(error) => error, - }; - assert!(!error.to_string().contains("secret")); - assert!(error.to_string().contains("no fallback")); -} diff --git a/docker-compose.ces-control.yml b/docker-compose.ces-control.yml deleted file mode 100644 index c881c8d..0000000 --- a/docker-compose.ces-control.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: endorphin-aster-vm - -services: - aster: - image: ${ASTER_IMAGE:?set ASTER_IMAGE to the verified release image} - init: true - ports: - - "127.0.0.1:${ASTER_PORT:-18081}:8080" - environment: - ASTER_LISTEN: 0.0.0.0:8080 - ASTER_STATE_DIR: /data/host - volumes: - - /var/lib/endorphin-aster-vm/host:/data/host - restart: unless-stopped - stop_grace_period: 20s - logging: - driver: json-file - options: - max-size: 10m - max-file: "3" - - postgres: - image: postgres:17-bookworm - environment: - POSTGRES_DB: aster - POSTGRES_USER: aster - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD} - volumes: - - /var/lib/endorphin-aster-vm/postgres:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U aster -d aster"] - interval: 5s - timeout: 3s - retries: 20 - restart: unless-stopped - stop_grace_period: 30s - logging: - driver: json-file - options: - max-size: 10m - max-file: "3" diff --git a/docker-compose.postgres.yml b/docker-compose.postgres.yml deleted file mode 100644 index 4d3acc8..0000000 --- a/docker-compose.postgres.yml +++ /dev/null @@ -1,31 +0,0 @@ -# Standalone alternative: docker compose -f docker-compose.postgres.yml up --build -d -services: - aster: - build: . - ports: - - "127.0.0.1:${ASTER_PORT:-8080}:8080" - environment: - ASTER_LISTEN: 0.0.0.0:8080 - ASTER_STATE_DIR: /data/host - volumes: - - aster-host:/data/host - restart: unless-stopped - - postgres: - image: postgres:17-bookworm - environment: - POSTGRES_DB: aster - POSTGRES_USER: aster - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env} - volumes: - - aster-postgres:/var/lib/postgresql/data - healthcheck: - test: ["CMD-SHELL", "pg_isready -U aster -d aster"] - interval: 5s - timeout: 3s - retries: 10 - restart: unless-stopped - -volumes: - aster-host: - aster-postgres: diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 2f84f5c..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,14 +0,0 @@ -services: - aster: - build: . - ports: - - "127.0.0.1:${ASTER_PORT:-8080}:8080" - environment: - ASTER_LISTEN: 0.0.0.0:8080 - ASTER_STATE_DIR: /data/host - volumes: - - aster-sqlite:/data - restart: unless-stopped - -volumes: - aster-sqlite: diff --git a/docs/README.md b/docs/README.md index d9f0d52..80091b0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,49 +1,22 @@ # 文档索引 -这里保存与源码一起版本管理的全局说明。当前页面负责找入口;局部文件职责由对应代码目录 README 维护。阅读次序:系统架构 → 运行时主题 → 对应源码目录。运行部署则从 operations 进入。 - -## 按问题查找 - -| 想知道什么 | 权威说明 | 代码入口 | +| 问题 | 权威说明 | 代码 | | --- | --- | --- | -| 各个 crate 如何分层,哪些能力已实现 | [架构](architecture/README.md) | [crates](../crates/README.md) | -| 解释型为何生成字节码,资源修订与平台升级如何区分 | [执行与版本模型(设计方向)](architecture/execution-and-versions.md) | [frontend](../crates/frontend/README.md)、[core](../crates/core/README.md);最小源码执行已接入,平台资源版本管理未实现 | -| EndorObject、对象头、句柄、默认操作 | [对象模型](runtime/objects.md) | [objects/src](../crates/objects/src/README.md) | -| Number、String、Dict、Cell、Function、异常数据 | [基本对象](runtime/builtins.md) | [objects/src](../crates/objects/src/README.md) | -| 字符串、文件、流与 endor URI 如何进入 lexer | [Endor 阅读器](runtime/reader.md) | [reader.rs](../crates/frontend/src/reader.rs)、[source.rs](../crates/frontend/src/source.rs) | -| 源码如何分词、PEG 规则、AST 和诊断 | [源码前端](runtime/frontend.md) | [lexer.peg](../crates/frontend/src/lexer.peg)、[grammar.peg](../crates/frontend/src/grammar.peg)、[frontend](../crates/frontend/README.md)、[CLI](../crates/cli/README.md) | -| AST 根/声明/语句、ASDL 生成、遍历、位置与 JSON/编辑器接口 | [AST 模型](runtime/ast.md) | [Endor.asdl](../crates/frontend/Endor.asdl)、[AST 实现](../crates/frontend/src/ast/README.md)、[editor](../crates/frontend/editor/README.md) | -| 名称/作用域分析、源码到字节码和 VM、可执行示例 | [编译链路](runtime/compiler.md) | [compiler](../crates/compiler/README.md)、[CLI](../crates/cli/README.md) | -| 操作码、代码验证、局部槽、函数定义与调用协议 | [字节码 VM](runtime/bytecode.md) | instruction.rs/code.rs 在 [objects/src](../crates/objects/src/README.md),frame.rs/eval.rs 在 [core/src](../crates/core/src/README.md) | -| 基础信息保存在哪里、旧库如何迁移 | [宿主文件信息](operations/host-information.md) | [host](../crates/host/README.md) | -| UTF-8 在日本地区是否适用,其他编码怎样进入 VM | [文本编码](runtime/text-encoding.md) | [core](../crates/core/README.md)、[host](../crates/host/README.md) | -| 数据库连接给谁用,是否自动建表或保存基础信息 | [数据库特殊类](runtime/database-objects.md) | [storage](../crates/storage/README.md) | -| VM、对象与执行帧何时创建/释放,循环如何回收 | [生命周期](runtime/lifecycle.md) | heap.rs 在 [objects/src](../crates/objects/src/README.md),eval.rs 在 [core/src](../crates/core/src/README.md) | -| LC_CTYPE、UTF-8、显式数据库配置和执行限额 | [配置](operations/configuration.md) | [core](../crates/core/README.md)、[storage](../crates/storage/README.md)、[server](../crates/server/README.md) | -| CLI/HTTP 怎么运行,数据库在哪里使用 | [运行](operations/README.md) | [cli](../crates/cli/README.md)、[server](../crates/server/README.md) | -| Docker、PostgreSQL、ces-control、回退与数据目录 | [部署](operations/deployment.md) | [scripts](../scripts/README.md)、[工作流](../.gitea/workflows/README.md) | -| 检查覆盖什么,CI 中哪里查看 Endor 实际输出与 artifact | [验证](operations/validation.md) | [示例清单](../crates/compiler/examples/cases.json)、[报告脚本](../scripts/endor_examples.py)、[core/tests](../crates/core/tests/README.md)、[storage/tests](../crates/storage/tests/README.md) | -| 改代码后文档写在哪里、怎样避免失效 | [维护规则](contributing/README.md) | 各目录 README 与本索引 | +| 引擎与宿主边界 | [架构](architecture/README.md) | [crates](../crates/README.md) | +| 解释执行、资源修订与引擎升级 | [执行与版本](architecture/execution-and-versions.md) | frontend/compiler/core | +| EndorObject、句柄、默认操作 | [对象模型](runtime/objects.md) | objects/src | +| Number、String、Dict、Cell、Function | [基本对象](runtime/builtins.md) | objects/src | +| 源码读取与来源 | [Reader](runtime/reader.md) | frontend/src/reader.rs | +| 词法、语法和诊断 | [Frontend](runtime/frontend.md) | frontend/src/lexer.peg/grammar.peg | +| ASDL、AST 与编辑器接口 | [AST](runtime/ast.md) | frontend/Endor.asdl | +| 名称分析、编译和装载 | [Compiler](runtime/compiler.md) | compiler/src | +| 字节码、函数与限额 | [字节码 VM](runtime/bytecode.md) | objects/src、core/src | +| 引用与 VM 生命周期 | [生命周期](runtime/lifecycle.md) | objects/src、core/src | +| UTF-8 与地区 | [文本编码](runtime/text-encoding.md) | frontend、objects | +| 嵌入及宿主入口 | [使用引擎](operations/README.md) | 独立宿主仓库 | +| 引擎配置边界 | [配置](operations/configuration.md) | core/src/frame.rs | +| CI 和验证边界 | [验证](operations/validation.md) | scripts、.gitea | +| 文档维护 | [维护规则](contributing/README.md) | 各目录 README | +| 后续开发交接 | [交接](contributing/handoff.md) | 实时源码和 Git 状态 | -## 多层目录 - -| 目录 | 负责的内容 | -| --- | --- | -| [architecture](architecture/README.md) | 系统边界、依赖方向、解释执行与平台资源版本方向 | -| [runtime](runtime/README.md) | 源码前端、对象、基本载荷、字节码和生命周期 | -| [operations](operations/README.md) | 运行、配置、部署和验证 | -| [contributing](contributing/README.md) | 开发定位方法与随代码更新文档的规则 | - -旧 outputs 的设计正文已融合到 runtime,重复入口和 outputs 目录已移除;CPython 研究与早期报告保存在本地忽略的 work/research,不纳入 Git。原 docs 平铺文档已迁入这些主题。过时启动模型、旧测试通过数量和机器资源快照已移除;历史内容需要时查 Git 历史,不混入当前行为说明。 - -## 仓库内查询 - -在仓库根目录使用静态文本检索: - -```sh -rg -n 'LC_CTYPE|UTF-8|DATABASE_URL' docs crates --glob '*.md' -rg -n 'EndorFrame|ReturnValue|collect_cycles' crates/core/src crates/objects/src -rg --files -g README.md -g '!work/**' -g '!outputs/**' -g '!target/**' -``` - -没有额外的文档服务器或搜索依赖。新增主题时更新此表;新增受版本管理的目录时增加 README,并从父目录 README 链接过去。 +目录入口:[architecture](architecture/README.md)、[runtime](runtime/README.md)、[operations](operations/README.md)、[contributing](contributing/README.md)。宿主部署、文件信息和数据库正文已移至 Hestia,命令行与实际输出报告已移至 Lyra;本仓库不保留重复正文。研究资料仍在忽略的 work/research,不提交。 diff --git a/docs/architecture/README.md b/docs/architecture/README.md index e7e767a..aec6faf 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -1,63 +1,24 @@ # 系统架构 -Endor 的目标是云原生语言;本仓库当前实现运行时、基础语法前端、最小源码编译链路和宿主演示,尚无云调度器或多租户执行沙箱。当前侧重核心语义独立于进程环境,以及数据库能力按对象需求显式提供。 - -## 本目录 +Aster 是可嵌入的 Endor 语言引擎,类似 V8 的职责边界;源码前端和编译器属于引擎。Lyra CLI、Hestia Server、Tessera GUI 是并列宿主。引擎自身没有常驻服务或生产部署。 | 文件 | 职责 | | --- | --- | -| README.md | 当前组件边界、依赖方向和跨组件约束 | -| [execution-and-versions.md](execution-and-versions.md) | 解释执行流程、资源修订/依赖锁与平台升级的设计方向,区分已实现与计划 | - -## 组件与依赖方向 +| README.md | 组件边界和依赖方向 | +| [execution-and-versions.md](execution-and-versions.md) | 平台解释执行、源码修订和引擎版本 | ```text -CLI ────→ Compiler + Core + Frontend + Host +Lyra / Hestia / Tessera → 按需链接 Aster 库 Compiler → Frontend + Core -Core ───→ Objects -Objects → 标准库 +Core → Objects +Objects → Rust 标准库 Frontend → peg + serde + serde_json -Server ─→ Core + Host -未来数据库特殊类的宿主实现 ─→ Storage ``` -CLI、Server 依赖 Host 保存文件信息,不依赖 Storage;Core 只依赖本地 Objects,Objects 无依赖且不反向依赖 Core;二者均不依赖 Storage。Compiler 负责名称/作用域分析、wordcode 生成与代码对象装载,CLI run/eval 连接源码执行;CLI 的 parse 命令通过 Frontend 的 EndorReader 读取文件/stdin,再解析 AST;Frontend 使用 peg 及工作区已有 serde/serde_json,读取适配使用标准库;Endor.asdl 在开发时生成 AST、遍历器与编辑器接口,普通构建不运行生成器。平台 URI 的存储与授权由宿主 provider 提供,Core 不依赖 Frontend。HTTP、Tokio、SQLx、数据库、网络和浏览器能力不进入 core 或 objects;两包均无第三方依赖。包与文件定位见 [crates](../../crates/README.md)。 +Objects 拥有对象、稳定 Box、代际句柄、引用事务、私有堆和字节码数据;Core 只负责 VM、内部 EndorFrame、帧切换与求值循环。Core 不反向依赖 Frontend,也不依赖 HTTP、数据库或 GUI。Frontend 的 AST 生成产物随源码提交,普通构建不运行生成器。 -| 组件 | 当前职责 | 全局契约 | -| --- | --- | --- | -| Core | Vm、执行帧、调用协议与取指求值循环;对象方法委托到 Objects | [字节码 VM](../runtime/bytecode.md) | -| Objects | 核心对象、私有堆、ObjectStore、Number、代码对象、指令数据和静态验证;无求值器 | [对象模型](../runtime/objects.md) | -| Frontend | Endor 阅读器、UTF-8 字符 PEG lexer、token PEG parser、ASDL 生成的根/声明/语句/表达式 AST 与跨工具接口,不执行源码 | [源码前端](../runtime/frontend.md) | -| Compiler | AST 名称/作用域分析、wordcode 模板、代码对象装载;不引入默认语言类 | [编译链路](../runtime/compiler.md) | -| Host | 基础信息的逐键 UTF-8 JSON 文件与旧库导入 | [宿主信息](../operations/host-information.md) | -| Storage | 按显式配置建立 SQLite/PostgreSQL 连接池,不定义应用表 | [数据库特殊类](../runtime/database-objects.md) | -| CLI | 对象引导演示、源码解析/执行与文件信息命令 | [CLI](../../crates/cli/README.md) | -| Server | HTTP 存活检查、只读 VM 信息及文件版本记录 | [Server](../../crates/server/README.md) | -| Gitea/Docker | 双数据库适配验证、镜像构建、宿主与 PostgreSQL 资源部署 | [部署](../operations/deployment.md) | +宿主以子模块固定 Aster 提交,并通过 Cargo 路径依赖编译到宿主进程中。每个 Vm 拥有独立 ObjectStore。ObjectRef 不跨 VM 或进程使用;EndorFrame 是语言调用帧,不是 GUI 页面。宿主决定线程、进程、输入、显示与执行任务之间的边界。 -core 保留既有对象类型的重导出与 Vm 方法,避免宿主导入方式随拆包改变。核心对象的定义和引用事务只在 objects 实现;ObjectRef 字段、Heap 和载荷替换不对包外开放。代码/指令属于可共享的执行数据,放在 objects;如何执行这些数据由 core 决定。定位见 [objects](../../crates/objects/README.md)。 +引擎从空堆启动,不预设 Python type/元类或语言根类。代码共享不可变对象,函数用显式帧向量执行;对象返回值拥有根引用,借用读取不自动 retain。Number 保留无损表示,数学运算语义尚未实现。现有帧数/指令数限制不构成内存配额或沙箱。 -## 当前契约 - -- Vm 从空堆启动。EndorObject 是实现层公共结构,不是语言根类;没有 Python type、继承或元类。 -- 对象地址稳定,公开句柄校验 VM 身份和槽位代数;句柄不是跨进程 ID。 -- 引用计数负责通常的释放,显式全堆追踪负责不可达循环;执行帧中的对象值也作为根。 -- 函数共享不可变代码对象,既可由宿主工厂创建,也可由 MakeFunction 字节码绑定默认值与闭包后创建;语言调用使用显式帧向量;字节码参考 CPython 的职责划分,不兼容其操作码 ABI。 -- Number 优先无损表示;数值算术、相等和哈希尚未定义。异常对象保存数据,尚无 raise/try/except 传播。 -- 内核不读取环境或修改 locale。宿主处理监听和信号;数据库配置由真正消费它的对象适配层显式传入,见 [配置](../operations/configuration.md)。 - -## 数据与服务边界 - -数据库用于未来某种特殊类,不保存基础信息、版本、部署记录或普通对象。Storage 连接不建应用表,不提供统一 KV;实际数据布局和查询由特殊类定义。当前只有连接适配,还没有实现该类或把它接入 Vm。 - -HTTP 宿主通过 Arc 共享只读 Vm。GET /bootstrap 只读对象模型和数量,GET /health 仅报告 HTTP 可响应。没有语言执行、对象写入或认证协议,不能称为远程执行平台。重启仍创建新空 VM,没有堆快照。 - -部署保留既有 PostgreSQL 服务与数据目录,应用启动不依赖数据库连接。基础信息从数据库改为 Host 文件层;发布脚本一次性只读导入旧 PostgreSQL 记录,不删除原表或卷。脚本结果未主动跟踪,不能据此认定远端已经完成迁移。 - -## 执行与版本方向 - -Endor 的目标流程是源码经阅读器、lexer、parser 和内部字节码生成后,由 VM 解释执行。源码资源与依赖版本由平台管理,不要求真实代码文件或项目级 Git 作为语言执行单位;使用者无需单独构建程序。解释器由平台统一升级,升级实现不改写用户源码修订。资源身份、修订与依赖锁尚未实现,持久化字节码缓存暂不作为首版要求;完整说明见 [解释执行与平台资源版本](execution-and-versions.md)。 - -## 后续边界 - -特殊类接口与异步数据库调用、AST 到字节码的编译与名称/类型分析、更多指令、异常传播、自动 GC、内存配额、取消和能力隔离等均需按实际需求设计。当前帧/指令限额不等于完整沙箱。返回 [文档索引](../README.md)。 +宿主文件信息和数据库适配现归 [Hestia](https://gitea.entroia.com/Zayden-Jung/endorphin-hestia-server),不保存 VM 堆。原生产服务、ASTER_* 配置、数据库、卷和路径由 Hestia 保持兼容;拆仓本身不迁移或删除这些资源。Aster CI 只验证引擎,数据库/HTTP/CLI 检查分别由宿主 CI 承担。返回 [文档索引](../README.md)。 diff --git a/docs/architecture/execution-and-versions.md b/docs/architecture/execution-and-versions.md index 267cb9e..6d456a0 100644 --- a/docs/architecture/execution-and-versions.md +++ b/docs/architecture/execution-and-versions.md @@ -13,7 +13,7 @@ Endor 由平台解释执行,目标流程为: 生成字节码是解释器内部的执行准备,可以称为内部编译。使用者提交或调用源码资源,由平台完成这些步骤,无需先构建独立程序。现有 VM 解释 wordcode,源码编译沿用该执行器。 -Frontend 产出 AST,由独立 Compiler 分析名称并生成代码模板,装载后交给 Core 执行。`cli run` 执行基础函数模块,`cli eval` 执行表达式,`cli parse` 继续只输出 AST;支持范围见 [编译链路](../runtime/compiler.md)。源码闭包与数值算术尚未接入,没有 CLI REPL。仓库中的 Cargo/Docker 构建用于构建 Rust 实现和宿主,不是 Endor 使用者的程序构建步骤。 +Frontend 产出 AST,由独立 Compiler 分析名称并生成代码模板,装载后交给 Core 执行。`lyra run` 执行基础函数模块,`lyra eval` 执行表达式,`lyra parse` 继续只输出 AST;支持范围见 [编译链路](../runtime/compiler.md)。源码闭包与数值算术尚未接入,没有 CLI REPL。仓库中的 Cargo/Docker 构建用于构建 Rust 实现和宿主,不是 Endor 使用者的程序构建步骤。 ## 名称、资源与修订 diff --git a/docs/contributing/README.md b/docs/contributing/README.md index 0331df4..297ba76 100644 --- a/docs/contributing/README.md +++ b/docs/contributing/README.md @@ -20,13 +20,9 @@ | AST 节点/根模式、ASDL 生成、Visitor、位置与 JSON/编辑器接口 | [ast.md](../runtime/ast.md) | Endor.asdl、生成器、生成产物与 AST/CLI 检查 | | 名称/作用域、源码编译、代码装载与执行示例 | [compiler.md](../runtime/compiler.md) | compiler 源码/检查/示例、CLI 与运行指南 | | 指令编码、栈效果、调用协议与执行限制 | [bytecode.md](../runtime/bytecode.md) | instruction/code/frame/eval 文件职责 | -| 宿主基础信息、JSON 文件和旧库导入 | [host-information.md](../operations/host-information.md) | host、cli、server 与脚本 README | | 地区与文本编码边界 | [text-encoding.md](../runtime/text-encoding.md) | 配置主题与实际 I/O 入口 | -| 数据库特殊类、显式连接和数据模型归属 | [database-objects.md](../runtime/database-objects.md) | storage 的源码与检查 README | | VM/帧/对象的创建、错误退出与销毁 | [lifecycle.md](../runtime/lifecycle.md) | 对象与字节码主题链接 | | 环境变量、默认值、优先级、UTF-8、locale 和配置边界 | [configuration.md](../operations/configuration.md) | 相关 crate README 链接 | -| 运行命令、端点和数据库连接边界 | [operations/README](../operations/README.md) | cli/server/storage README | -| 镜像、CI、部署、回退和数据目录 | [deployment.md](../operations/deployment.md) | scripts/工作流 README | | 检查入口、覆盖范围、结果边界 | [validation.md](../operations/validation.md) | 各 tests README | | 不可变研究表、聊天导出、临时脚本或构建产物 | 工作区外输出或忽略目录 | 不作为当前项目契约、不提交研究 HTML | @@ -37,7 +33,7 @@ 1. 新增、删除、重命名文件或目录:更新本目录和父目录 README 的文件表;新增受版本管理目录必须有 README。该规则不覆盖 .git、target、data、work、outputs 或其他忽略产物。 2. 修改 API、默认值、所有权、操作码或错误处理:同时修改上表对应主题、示例与相关检查;区分已实现行为和未来设计。 3. 改主题路径:修复仓库所有内部链接,更新 docs 总索引;失效旧页直接删除,不长久保留一份旧契约。 -4. 改宿主配置/部署:同步配置表、运行命令和脚本 README;不把更换应用容器写成恢复 VM 堆或数据库回退。 +4. 改引擎验证配置:同步验证主题和脚本 README;宿主配置与部署由各宿主仓库维护。 5. 检查链接、目录覆盖、旧路径和差异空白;仅在指定远端运行行为测试。没有查询 CI 结果就写未核实,不能继承上次通过的结论。 不是每次提交都要给所有文档改日期或复制版本号;只有相关内容发生变化时更新相应文档。提交及代码审查描述说明目的和验证,文档保留长期可查询的当前行为。 diff --git a/docs/contributing/handoff.md b/docs/contributing/handoff.md index e15067b..3ee5349 100644 --- a/docs/contributing/handoff.md +++ b/docs/contributing/handoff.md @@ -1,49 +1,30 @@ # Aster VM 后续开发交接 -从 [文档索引](../README.md) 查询当前实现,再按目录 README 找文件。不要把旧聊天、早期报告或未查询的 CI 状态当成当前契约。 +先读 [文档索引](../README.md)、本地 AGENTS.md 和实时 Git 状态。所有本项目对话标题含 Aster VM。 -## 当前项目与命名 +## 项目边界 -仓库为 endorphin-aster-vm,归属 ProjectEndorphin。包与目录为 core、objects、frontend、compiler、storage、host、cli、server;core 的 Rust 库名为 endor_core,命令为 cli/server。旧部署服务、数据库和路径保留既有标识以保护数据。 +Aster 是可嵌入引擎,只保留 objects、core、frontend、compiler。Objects 零外部依赖,Core 仅依赖 Objects;不开放私有句柄构造,不把对象引用事务搬回 Core。Reader、PEG 与 ASDL 源码链路属于引擎;网络、文件基础信息、数据库和 GUI 属于宿主。 -## 当前包边界 - -Objects 零依赖,统一拥有核心对象、Number、代码对象/指令数据与静态验证、私有堆、ObjectStore 和共享 VmError;不依赖 Core。Core 仅依赖本地 Objects,保留 Vm、执行帧、取指求值循环;原有对象方法委托给 ObjectStore,并重导出既有对象类型。不要把对象定义或引用事务搬回 Core,也不开放私有句柄构造。源码导航见 [objects](../../crates/objects/README.md) 与 [core](../../crates/core/README.md)。 +[Lyra](https://gitea.entroia.com/Zayden-Jung/endorphin-lyra-cli) 接收 CLI 和示例实际输出报告;[Hestia](https://gitea.entroia.com/Zayden-Jung/endorphin-hestia-server) 接收 HTTP、兼容管理命令、Host、Storage 与既有 Docker 部署。宿主使用固定提交的 Aster 子模块。Aster 保留 ces-control 引擎 CI,移除 CD;不删除远端服务、数据库、卷或旧部署目录。Tessera 可本地编译运行但不本地测试,其 CI 同样使用 ces-control。Aster/Lyra/Hestia 不在本地构建测试,不主动查询 CI/部署。 ## 已实现 - EndorObject、稳定 Box、代际句柄、显式强引用及手动循环回收;VM 从空堆启动,没有 Python type/元类或语言根类。 - Function、NativeFunction、Dict、Cell、异常数据、UTF-8 String 和无损表示优先的 Number。 - 小型 wordcode、共享代码对象、操作数/控制流/栈校验、显式执行帧及 execute/call;MakeFunction 可在执行中创建带默认参数与共享闭包 Cell 的函数。对象返回值持有根,用后 release_value;借用读取不自动 retain。 -- EndorReader 在 lexer 前提供字符串/流/文件/宿主 URI provider 输入,产出不可变 SourceUnit,保存来源与不透明修订标签;带来源的词法/语法错误保留行列,CLI parse 支持文件和 stdin。没有平台 URI 后端或版本锁实现,见 [阅读器](../runtime/reader.md)。 +- EndorReader 在 lexer 前提供字符串/流/文件/宿主 URI provider 输入,产出不可变 SourceUnit,保存来源与不透明修订标签;带来源的词法/语法错误保留行列,Lyra parse 支持文件和 stdin。没有平台 URI 后端或版本锁实现,见 [阅读器](../runtime/reader.md)。 - Endor.asdl 是 AST 唯一定义源,生成 Rust 节点/Spanned/Visitor、TypeScript 类型/遍历器和 JSON Schema。AstRoot 支持 Module/Expression/Interactive,Program/Block 使用声明/语句列表;AstDocument 提供带 schema 的 JSON 及范围校验,CLI 支持 --mode/--json,见 [AST 模型](../runtime/ast.md)。生成产物需通过 scripts/generate_ast.py 更新,不能手改。 - Frontend 使用 peg 0.8.6,词法规则在 lexer.peg(字符 → Token),语法规则在 grammar.peg(Token → AST);lexer.rs 保留逐 Token 游标与诊断位置换算,提供完整输入解析、基础函数/表达式 AST、Unicode 字节范围与行列诊断;CLI parse 可读 UTF-8 文件/stdin 输出 AST,不执行源码。当前语法以未完成设计稿为起点,可以继续调整,见 [源码前端](../runtime/frontend.md)。 -- Compiler 已连接 AST → 模块/函数名称与初始化检查 → wordcode 模板 → VM 代码对象;CLI run 调用模块无参数入口,eval 执行表达式。Compiler 不依赖语言类注册;无损常量、函数/参数/let/调用和宿主成员可执行,详见 [编译链路](../runtime/compiler.md) 与 [示例](../../crates/compiler/examples/README.md)。 -- Host 文件信息独立于数据库:host.version、deployment.release 与 metadata put/get 使用 UTF-8 JSON。旧 PostgreSQL 基础信息只读导入缺失文件,不删除原库,见 [文件信息](../operations/host-information.md)。 -- Storage 只提供显式 DatabaseConfig/DatabaseConnection;没有默认数据库、自动应用建表或基础信息 KV。特殊类的名称/接口尚未定义,没有将连接接入 VM。 -- CLI 提供引导、解析、源码执行与文件信息,Server HTTP 存活/只读 VM 信息;生产 PostgreSQL 作为未来特殊类的预置资源保留。 -- CI 使用 examples/cases.json 执行全部 Endor 示例,保存实际 stdout/stderr、退出码及状态;对应 run 页面展示 Markdown,并上传 endor-execution-output- artifact(Markdown/JSON,保留 30 天),失败也保留报告。查看方式见 [验证](../operations/validation.md),本地不运行报告脚本。 +- Compiler 已连接 AST → 模块/函数名称与初始化检查 → wordcode 模板 → VM 代码对象;Lyra run 调用模块无参数入口,eval 执行表达式。Compiler 不依赖语言类注册;无损常量、函数/参数/let/调用和宿主成员可执行,详见 [编译链路](../runtime/compiler.md) 与 [示例](../../crates/compiler/examples/README.md)。 -## 资料位置 -正式说明在 docs,多层 README 负责导航;源码目录 README 描述本目录。outputs 已移除,CPython 3.14 的 284 项对象研究表、生成脚本及早期历史报告保留在本地忽略的 work/research,不上传 Git。 +## 当前缺口 -## 尚未实现 - -平台资源存储/修订与依赖锁、闭包捕获与类型分析、源码闭包声明、导入/类/Agent 语法、迭代器等扩展指令、数值算术/相等/哈希、完整异常传播、可变 VM/异步数据库回调、生成器、自动 GC、堆快照、内存配额、取消及不可信代码沙箱。宿主回调 panic 后的执行根恢复也未完成。不能把未来设计写成已有能力。 +平台资源版本/依赖锁、源码闭包、数值算术、完整异常传播、内存配额、可靠取消、不可信代码沙箱、自动 GC 和宿主 panic 后执行根恢复尚未完成。类型注解只保存语法,不引入 Python type/元类。UI 接入适配由 Tessera 实现,不为 Tessera 修改引擎接口。 ## 工作约定 -本地不构建、不运行测试/冒烟,不下载依赖;允许静态阅读、格式化、ASDL 源码生成/同步检查、语法/链接检查。运行验证交给 ces-control 的 SQLite、真实 PostgreSQL 与宿主流程。按用户要求不主动跟踪 CI/部署,结果未查询就明确写未核实。 +中文沟通,源码与文档同版本维护。所有受版本管理目录维护 README,新增/删除/移动同步当地与父目录入口,跨目录契约在 docs 维护唯一正文。AGENTS.md 与 work/research 保持本地忽略。Gitea 仅用 codex-bot,隔离 SSH、仓库级作者身份及提交后中文报告按本地约定执行。 -数据库、卷和已有资料保留;不得改其他项目或共享 Runner。研究材料与 AGENTS.md 不提交。Git 身份按本地工作约定使用专用 codex-bot,不能借用仓库所有者凭据。 - -所有本项目对话标题包含 Aster VM。文档与代码同版本维护,不重新建立 outputs 或重复正文目录。开始下一项任务前先看 git status/最新提交,读取本地 AGENTS.md;本页不代替实时状态。 - -## 已确认的设计方向 - -Endor 由平台解释执行;AST 到字节码是解释器内部准备步骤,使用者无需先构建程序。代码未来以 workspace/namespace 下的资源提供,通过 Endor URI 查找,不要求真实源码文件或项目级 Git。资源修订与依赖锁选择用户代码,平台统一升级解释器;升级实现不自动改写源码修订。版本信息主要属于资源与依赖元数据,URI 具体格式、hash 方案和发布快照仍待设计。首版源码执行暂不要求持久化字节码缓存,VM 内代码对象共享继续保留。完整方向与尚未实现的边界见 [执行与版本模型](../architecture/execution-and-versions.md)。 - -## 下一步 - -最小源码执行链路已连接;下一步可补数值算术指令与明确的数值语义、逐指令源码映射,再按需求开放块语句和嵌套函数/闭包。现有 Interactive 编译接口允许宿主复用命名空间,但没有 CLI REPL。参数与返回类型仍只保存为语法节点,不引入 Python type/元类或借此实现整个语言类型系统。语法不必等外部设计稿全部定稿,先扩展可用子集。新增测试已交给既有远端 workspace 流程,本地不运行,也不主动查询 CI。 +Endor 由平台解释执行,AST 到字节码是内部步骤;源码资源修订与解释器升级分开管理,具体 URI、资源锁与持久化协议仍待设计,见 [执行与版本](../architecture/execution-and-versions.md)。下一步按具体语言需求完善引擎,宿主调度和渲染在各宿主仓库推进。 diff --git a/docs/operations/README.md b/docs/operations/README.md index d74530d..b6d5a81 100644 --- a/docs/operations/README.md +++ b/docs/operations/README.md @@ -1,63 +1,15 @@ -# 运行宿主 +# 使用引擎 -CLI 提供 VM 引导、源码解析/执行及文件基础信息,Server 提供状态与文件基础信息,二者不连接数据库。Storage 为未来数据库特殊类保留显式连接能力,见 [数据库对象](../runtime/database-objects.md)。以下构建和运行命令仅供用户指定的远端环境,本地不执行。 +Aster 工作区只产出 Rust 库,不包含 CLI 或 HTTP 可执行文件。宿主在自己的进程中创建 Vm,按需使用 Reader、Frontend 和 Compiler;宿主构建会自动编译所需引擎库。 -## CLI - -下列 Cargo 构建针对 Rust 实现与宿主;Endor 的设计是由平台自动准备字节码并解释执行,无需使用者自行构建程序。当前已有最小 run/eval 源码执行入口,见 [执行与版本模型](../architecture/execution-and-versions.md)。 - -工作区要求 Rust 1.91 或兼容更高版本,镜像固定 rust:1.91.1-bookworm,依赖由 Cargo.lock 锁定。 - -```sh -cargo build --workspace --locked -cargo run -p cli -- bootstrap -cargo run -p cli -- help -cargo run -p cli -- parse crates/frontend/examples/basic.endor -cargo run -p cli -- parse - < crates/frontend/examples/basic.endor -cargo run -p cli -- parse --json crates/frontend/examples/basic.endor -cargo run -p cli -- run crates/compiler/examples/hello.endor -cargo run -p cli -- run --entry main crates/compiler/examples/calls.endor -cargo run -p cli -- eval - < crates/compiler/examples/literal.endor -``` - -bootstrap 创建两个 Record,输出 runtime=EndorObject、type_system=disabled、objects_distinct=true 和 heap_objects=2,显式释放后输出 released heap_objects=0。默认无参数执行同一示例。 - -parse 通过 EndorReader 严格读取 UTF-8 源码文件;`parse -` 从 stdin 读到 EOF,输入来源为 ``,没有 REPL 提示符或会话求值。默认解析 module 模式,成功时输出 Rust Debug 格式的 AstRoot;--mode 支持 expression/interactive,--json 输出带 schema 的 JSON,见 [AST 模型](../runtime/ast.md);失败时输出来源、行、列并返回非零退出码;阅读错误在进入 lexer 前返回。输入适配与 URI provider 见 [阅读器](../runtime/reader.md)。该命令只解析语法,不执行源码;语法与当前边界见 [源码前端](../runtime/frontend.md)。 - -run 编译模块、初始化函数并调用无参数入口(默认 main),--entry 可指定名称;eval 执行一个完整表达式。二者支持文件或 - 读取 stdin 到 EOF,输出返回值,失败不输出成功结果。支持范围、命名空间释放及 traceback 精度见 [编译链路](../runtime/compiler.md),真实 Endor 输入见 [可执行示例](../../crates/compiler/examples/README.md);frontend/basic.endor 含算术,仍仅作为解析样例。 - -CLI 不读取数据库环境,不创建基础信息数据库表。metadata put/get 通过 host 包写 UTF-8 JSON 文件,metadata import 仅导入缺失记录;示例、配置和协议见 [宿主信息](host-information.md)。输入与退出码见 [cli/src](../../crates/cli/src/README.md)。 - -## HTTP - -```sh -cargo run -p server -curl --fail http://127.0.0.1:8080/health -curl --fail http://127.0.0.1:8080/bootstrap -``` - -Server 创建空 VM 并绑定监听地址,默认 127.0.0.1:8080;通过 host 包写入文件 host.version,不读取数据库 URL、不建表。 - -| 端点 | 行为 | +| 入口 | 位置与职责 | | --- | --- | -| GET /health | 返回 200 和 status=ok;仅表明 HTTP 宿主存活,不包含数据库后端字段 | -| GET /bootstrap | 返回 object_model=EndorObject、type_system=false、heap_objects=0;只读、不分配对象 | +| [Lyra CLI](https://gitea.entroia.com/Zayden-Jung/endorphin-lyra-cli) | lyra run/eval/parse/bootstrap;命令行文件与 stdin 行为及实际输出报告 | +| [Hestia Server](https://gitea.entroia.com/Zayden-Jung/endorphin-hestia-server) | HTTP、管理命令、文件信息、数据库适配、Docker 运维 | +| Tessera GUI | 原生窗口、布局、绘制和输入;本地可编译运行,但测试交给 CI | -没有语言执行、对象写入或认证 API。Unix 上 Ctrl-C/SIGTERM 触发 Axum 优雅退出,最后的 Arc 释放时销毁 Vm;没有数据库池需要在此处关闭,也没有执行中脚本的取消协议。 +宿主固定子模块提交,再在 Cargo 中引用 crates/core(库名 endor_core)、crates/frontend、crates/compiler。源码编译与返回值根引用契约见 [Compiler](../runtime/compiler.md)、[生命周期](../runtime/lifecycle.md)。宿主不能把另一个 VM 的句柄传入当前 VM。 -## 数据库能力的使用位置 +引擎没有环境变量、locale 初始化、数据库连接或后台服务。宿主负责源码来源、globals、限额、调度和释放。Aster 本地不构建或测试;验证命令只在 ces-control 执行,见 [验证](validation.md)。旧部署运行资源继续由 Hestia 管理,移除本仓库 CD 不会删除资源。 -当前应用不把数据库作为启动依赖;即使进程带有失效 DATABASE_URL 或 SQLITE_PATH,也不会因此读取它们。特殊类将来需要连接时,宿主适配实现显式传入 DatabaseConfig,详见 [配置](configuration.md)。 - -Storage 支持 SQLite 文件/内存和 PostgreSQL,不自带应用 schema 或 key/value 数据模型。旧数据库数据保留原位,发布时只读导入到文件信息;新的普通宿主不会继续写入数据库;数据持久化也不自动保存 VM 堆、句柄或帧。 - -## 定位问题 - -| 现象 | 定位 | -| --- | --- | -| Server 无法绑定地址 | ASTER_LISTEN 与端口占用,见 [配置](configuration.md) | -| /health 正常但数据库不可用 | health 不检测数据库;当前没有使用数据库的语言类 | -| 重启后 VM 对象消失 | 当前未实现 VM 快照,见 [生命周期](../runtime/lifecycle.md) | -| 需要连接或查询数据库 | [storage](../../crates/storage/README.md) 和 [数据库对象](../runtime/database-objects.md);先定义实际特殊类接口 | - -容器与预置 PostgreSQL 见 [部署](deployment.md),检查范围见 [验证](validation.md)。返回 [文档索引](../README.md)。 +本目录:[README.md](README.md) 描述嵌入入口,[configuration.md](configuration.md) 描述配置边界,[validation.md](validation.md) 描述检查。返回 [文档索引](../README.md)。 diff --git a/docs/operations/configuration.md b/docs/operations/configuration.md index c51805d..7b61119 100644 --- a/docs/operations/configuration.md +++ b/docs/operations/configuration.md @@ -1,73 +1,9 @@ -# 配置、UTF-8 与进程边界 +# 引擎配置边界 -## 当前选择 +Aster 的 objects、core、frontend、compiler 不读取进程环境或修改 locale。输入文本为 UTF-8,其他编码由宿主在创建 SourceUnit 前转换,见 [文本编码](../runtime/text-encoding.md)。 -当前先明确少量稳定边界,不引入 PyPreConfig/PyConfig 式两阶段初始化,也不新增 VM 配置文件、环境变量前缀或配置框架。Vm::new 从空堆启动;监听与网络由宿主处理,数据库连接仅由真正消费它的对象显式请求。 +Vm::new 创建独立空堆。宿主显式提供 globals 和必要对象;CompileOptions.globals 只声明可见名称,不注入实际值。execute/call 的对象结果拥有根,使用后 release_value。 -| 配置层 | 当前入口 | 责任 | -| --- | --- | --- | -| 内核 | Vm::new、显式创建对象/函数;call_with_limits/execute_with_limits | 可预测的运行时语义,不读取环境变量、修改 locale、注册信号或加载 .env | -| 宿主 | Server 启动代码与 host::MetadataStore | 读取监听/文件信息目录;基础信息存 JSON 文件,不读取数据库配置 | -| 数据库对象适配 | DatabaseConnection::connect(&DatabaseConfig) | 显式选择后端后连接,不预设应用表;特殊类尚未接入 | -| 部署 | Compose 与 ces-control 脚本 | 端口映射、数据库密码、持久卷和镜像版本 | -| 构建/CI | Cargo.toml、Cargo.lock、Dockerfile、工作流 | 工具链、依赖锁定与验证环境;不作为语言运行语义配置 | +ExecutionLimits 默认 max_frames=1000、max_instructions=usize::MAX。嵌入方可通过 execute_with_limits/call_with_limits 显式设置有限值。没有内存配额、可靠取消或完整沙箱;原生回调耗时不由指令数限制覆盖。具体契约见 [字节码 VM](../runtime/bytecode.md)。 -内核不直接依赖 HTTP、SQLx 或操作系统 locale 接口。这个隔离来自现有代码与分层,不是已经实现的独立安全沙箱。 - -## 为什么不照搬 CPython 的 locale 初始化 - -CPython 的 PyPreConfig 支持选择分配器、UTF-8 模式以及是否配置 LC_CTYPE,以处理它自己的启动和兼容性需求。Python 官方也提供忽略环境、保留 LC_CTYPE、不注册信号处理的隔离初始化。当前 Endor 通过 Rust 宿主接收已构造的值,没有需要复刻这套预初始化的依赖环。[CPython 初始化文档](https://docs.python.org/3.14/c-api/init_config.html)。 - -LC_CTYPE 处理字符分类、大小写和多字节字符相关约定;小数点和数字分组属于 LC_NUMERIC。只调整 LC_CTYPE 不能直接等同于修改数字小数点,这两类配置应区分。[locale 类别说明](https://docs.python.org/3.14/library/locale.html#locale.LC_CTYPE)。 - -云原生环境仍会遇到进程变量、终端、文件路径与外部工具的问题;较少跨语言互操作不代表可以省略输入边界。当前解决方式是让核心语义独立于进程区域设置,把必要的解码留在宿主入口。 - -## 当前文本和数字行为 - -- Value::Text 与 EndorStringObject 使用 Rust String,内容始终是有效 UTF-8。字符访问按 Unicode 标量值,不按字节或字形簇;不支持孤立代理码点。[Rust String](https://doc.rust-lang.org/std/string/struct.String.html#utf-8)。 -- Number 的整数与十进制构造器明确检查 ASCII 数字和语法;十进制点为句点,不根据 LANG/LC_NUMERIC 切换为逗号。当前数值比较、哈希和算术尚未实现。 -- 内核没有 setlocale 调用;不会为了启动 VM 修改 LANG、LC_ALL 或 LC_CTYPE。外部库、终端、字体或下游工具自身的行为不在这项保证之内。 -- CLI parse 通过 EndorReader 严格读取 UTF-8 文件/stdin,阅读器也接受字符串和宿主 URI provider;来源与解码失败契约见 [阅读器](../runtime/reader.md)。Frontend 提供基础标识符与语法解析,见 [源码前端](../runtime/frontend.md)。尚无通用字节串对象、语言文件系统 API 或非 UTF-8 源码自动转码入口。 - -日文与外部 Shift_JIS/CP932 的处理见 [文本编码](../runtime/text-encoding.md);内部 UTF-8 不因地区切换。 - -## 已有进程与部署变量 - -| 名称 | 使用位置与默认值 | 错误和优先级 | -| --- | --- | --- | -| ASTER_STATE_DIR | Host 文件信息目录;默认 data/host,容器 /data/host | 使用 var_os 保留操作系统路径;显式空值报错,不影响数据库选择 | -| ASTER_LISTEN | Server;默认 127.0.0.1:8080 | 当前 var(...).unwrap_or_else:缺失或非 Unicode 都用默认值;有效 Unicode 的错误地址在绑定时失败 | -| ASTER_PORT | Compose 宿主端口映射 | 独立方案默认 8080,ces-control 默认 18081;不是 Vm 配置 | -| POSTGRES_PASSWORD | PostgreSQL Compose/生产环境文件 | Compose 必填;示例使用随机十六进制以避免 URL 转义歧义;不写入仓库 | -| ASTER_IMAGE | ces-control Compose | 必填已构建的版本镜像,通常由 release.env 提供 | -| ASTER_RELEASE_SHA | CI 与部署脚本 | 必须为 40 位小写十六进制提交 SHA;用于镜像和版本目录 | -| ASTER_TEST_POSTGRES_URL | 指定环境显式运行 PostgreSQL 集成测试 | 专用测试库 URL;不是生产数据库配置 | - -文件信息命令及 Server 通过 host 包读取 ASTER_STATE_DIR,Server 还读取 ASTER_LISTEN;不会把 DATABASE_URL/SQLITE_PATH 作为基础信息存储配置。程序不自动加载 .env,Cargo 运行时须显式导出监听变量。Compose 会按其规则读取 .env;显式 --env-file 的生产文件由部署脚本管理。模板见 [.env.example](../../.env.example),执行链见 [部署](deployment.md)。 - -## 显式数据库配置 - -DatabaseConfig 没有默认值和 from_env,仅允许显式构造:Postgres(url)、SqliteFile(path)、SqliteMemory。没有默认 data/aster.db,也没有应用启动时的全局连接。connect 不执行应用 DDL,不保存基础信息;基础信息另由 [host 文件层](host-information.md) 保存;未来的特殊类负责消费连接和定义数据,见 [数据库对象](../runtime/database-objects.md)。 - -SQLite 文件显式连接时创建父目录与文件、启用 WAL,最多 5 个连接、busy timeout 5 秒;内存库单连接、不因池轮换销毁。PostgreSQL 最多 5 个连接、获取超时 5 秒,错误不降级。数据库 URL 的 Debug 和公开错误脱敏;底层错误 source 保留,不应不加处理地公开输出。 - -## 已有执行限额 - -ExecutionLimits 是每次调用显式传入的普通 Rust 值,不是进程全局开关;字段定义见 [frame.rs](../../crates/core/src/frame.rs)。 - -| 字段 | 默认值 | 作用 | -| --- | --- | --- | -| max_frames | 1000 | 本次执行允许的活动语言帧数量 | -| max_instructions | usize::MAX | 本次执行共享的逻辑指令预算;默认不设实用的有限预算 | - -call_with_limits/execute_with_limits 使用自定义值;嵌套语言调用共用计数。预算不能抢占正在执行的 Native 回调,也没有超时、内存上限或异步取消能力;不能据此视为安全执行不可信代码的完整方案。 - -## 保留的入口缺口与以后再做的内容 - -CLI 当前使用 std::env::args,遇到非 Unicode 命令行参数可能 panic,并没有统一转换成友好的配置错误。[Rust args 文档](https://doc.rust-lang.org/std/env/fn.args.html#panics)。Server 的非 Unicode ASTER_LISTEN 也尚未严格报错;宿主输入错误接口尚未统一。 - -以后增加宿主输入时,应明确使用 OsString/PathBuf 保留系统路径,或显式拒绝不符合文本协议的输入;不要通过修改整个进程 locale 让隐式解码暂时成功。未来源码文本默认 UTF-8、协议数字固定格式是设计方向,只有加载器实现后才能写成已实现行为。 - -现在不新增通用 VmConfig。出现构造期策略时再集中为实例级不可变配置;进程参数由宿主解析后传入,不让 Vm::new 自动读取一组隐含开关。时区、模块路径、凭据注入、内存配额和网络能力也按实际实现分别设计,不预设已存在。 - -返回 [运行指南](README.md) 或 [文档索引](../README.md)。 +ASTER_LISTEN、ASTER_STATE_DIR、数据库 URL 和部署配置属于独立宿主,不属于引擎设置。旧兼容标识由 Hestia 管理。返回 [使用引擎](README.md)。 diff --git a/docs/operations/deployment.md b/docs/operations/deployment.md deleted file mode 100644 index 5c773c0..0000000 --- a/docs/operations/deployment.md +++ /dev/null @@ -1,74 +0,0 @@ -# Docker 与 ces-control 部署 - -这里描述仓库当前工作流和脚本行为,不代表正在运行的服务器状态。生产保留预置 PostgreSQL 与既有数据。当前 VM 宿主不消费数据库,数据库连接仅为未来特殊类保留。配置字段见 [配置](configuration.md),CI 检查细节见 [验证](validation.md)。 - -## 镜像与独立运行 - -Dockerfile 的 source 阶段复制 Cargo 工作区及脚本;ci 阶段安装远端检查工具,build 阶段构建 server 和 cli,runtime 阶段使用 Debian slim、UID 10001、/data 工作目录及 HTTP 健康检查。 - -指定环境的独立宿主方案(沿用原 SQLite 卷,基础信息写入其中的 host 目录): - -```sh -cp .env.example .env -docker compose up --build -d -curl --fail http://127.0.0.1:8080/health -docker compose exec aster cli bootstrap -``` - -默认 docker-compose.yml 提供监听地址和 ASTER_STATE_DIR,保留 aster-sqlite 卷挂载;只写基础信息 JSON,不自动打开卷中的数据库。down 保留数据,down -v 会删除卷。 - -指定环境中同时预置 PostgreSQL 的独立方案:先在 .env 中将 POSTGRES_PASSWORD 改为随机十六进制字符串,例如 openssl rand -hex 24,再运行: - -```sh -docker compose -f docker-compose.postgres.yml up --build -d -docker compose -f docker-compose.postgres.yml exec aster cli bootstrap -``` - -仅使用该 Compose 文件,不与默认文件叠加。此方案使用 aster-postgres 命名卷、PostgreSQL 17,以及独立的 aster-host 文件信息卷;数据库不发布宿主端口。两个独立方案不要同时占用相同应用端口。未来特殊类跨不可信网络连接数据库时,需要明确配置 PostgreSQL TLS 验证。 - -## CI 到正式发布 - -[工作流](../../.gitea/workflows/README.md) 响应 main push 和 workflow_dispatch,在 ces-control 上串行运行: - -1. 检出代码,调用 ci-ces-control.sh;在 Docker 中检查 workspace(含 SQLite 适配)、Endor 示例实际输出、独立 HTTP 冒烟及专用真实 PostgreSQL。清理检查容器前复制报告到宿主 ci-output,工作流无论验证成功或失败均展示并上传 endor-execution-output- artifact;字段与位置见 [验证报告](validation.md)。 -2. 检查成功后构建 endorphin-aster-vm: 生产镜像。 -3. main 上调用 deploy-ces-control.sh 更新 VM 宿主并保留预置 PostgreSQL。 - -临时检查容器与网络退出时清理,Cargo 下载与构建缓存保留在专属远端卷。生产镜像、已有发布目录与数据库数据保留;没有自动 image prune 或数据库卷清理。工作流不改动其他项目或共享 Runner 配置。 - -## 生产目录与数据 - -Cargo 包名为 core/objects/frontend/compiler/storage/host/cli/server,core 的 Rust 库名为 endor_core,可执行文件为 cli/server。Compose 应用服务名 aster、数据库名、卷和生产路径是已有部署资源标识,运行命令为该服务中的 cli。应用不接收 DATABASE_URL、不依赖数据库健康才能启动;Compose 的整体 --wait 仍检查所预置 PostgreSQL 的就绪状态。 - -| 路径 | 用途 | -| --- | --- | -| /opt/endorphin-aster-vm/releases// | 固定版本的 compose.yml 与 release.env | -| /opt/endorphin-aster-vm/current | 最近通过部署检查的版本符号链接 | -| /etc/endorphin-aster-vm/production.env | 首次生成的数据库密码和默认端口,权限 0600 | -| /var/lib/endorphin-aster-vm/host | 文件基础信息,挂载到 /data/host,目录由部署准备并交给 UID 10001 | -| /var/lib/endorphin-aster-vm/postgres | 独立的 PostgreSQL 持久数据目录 | -| /var/lock/endorphin-aster-vm-deploy.lock | 避免并行切换生产版本的文件锁 | - -生产 Compose 固定项目名 endorphin-aster-vm,默认应用入口仅绑定 127.0.0.1:18081;数据库不发布端口,两个服务使用 unless-stopped 自动重启和日志轮换。端口绑定回环地址不等于已实现应用认证。 - -部署脚本只接受已构建镜像。容器就绪、HTTP status=ok 和 CLI 对象引导/释放检查都成功后才切换 current;发布记录改写到 deployment.release.json;首次发布一次性只读导入旧基础信息,再保留完成标记,不删除数据库原记录。详情见 [宿主信息](host-information.md)。镜像 SHA、release.env 与 current 链接负责标识版本。失败时保留数据并尝试恢复上一个健康版本;首次部署没有旧版本可恢复。脚本内容导航见 [scripts](../../scripts/README.md)。 - -这里的镜像 SHA、host.version 与 deployment.release 标识 Rust 实现/宿主发布,不是 Endor 用户资源的源码修订。平台方向是统一升级解释器,保留用户源码修订及明确的依赖选择;当前没有资源存储、依赖锁、持久化字节码缓存或执行帧热切换机制。两类版本的边界见 [执行与版本模型](../architecture/execution-and-versions.md)。 - -## 查看、停止与恢复 - -以下命令在服务器上使用已有生产配置,不重新生成密码: - -```sh -docker compose -p endorphin-aster-vm --env-file /etc/endorphin-aster-vm/production.env --env-file /opt/endorphin-aster-vm/current/release.env -f /opt/endorphin-aster-vm/current/compose.yml ps -curl --fail http://127.0.0.1:18081/health -curl --fail http://127.0.0.1:18081/bootstrap -``` - -同样的 Compose 参数接 stop 停服保留数据,接 up -d --wait 恢复。不要删除数据目录或使用删除卷的操作代替停服。 - -更改环境文件密码不会自动修改已有 PostgreSQL 数据库用户密码;需另行同步数据库凭据。数据库 major 升级、schema 变更需要迁移设计,恢复旧应用镜像不等于回退数据库。 - -Compose 配置依据 [Docker 环境变量说明](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/),触发方式依据 [Gitea Actions](https://docs.gitea.com/usage/actions/)。按用户约定不主动追踪运行状态;需要排障时针对具体提交核实,不能用旧成功记录推断新版本结果。 - -返回 [运行指南](README.md) 或 [文档索引](../README.md)。 diff --git a/docs/operations/host-information.md b/docs/operations/host-information.md deleted file mode 100644 index b514019..0000000 --- a/docs/operations/host-information.md +++ /dev/null @@ -1,47 +0,0 @@ -# 宿主基础信息:文件保存 - -基础信息不使用数据库。host 包保存逐键 UTF-8 JSON,CLI 提供读写,Server 启动写 host.version,部署检查通过后写 deployment.release。Storage 的数据库连接仅给未来特殊类使用。 - -## 文件与配置 - -ASTER_STATE_DIR 使用操作系统路径,默认 data/host;显式空值报错。读取一个不存在的键返回 None,不创建目录;写入时创建目录。容器为 /data/host;生产挂载到 /var/lib/endorphin-aster-vm/host,独立 PostgreSQL Compose 使用 aster-host 卷。 - -例如 host.version 对应 host.version.json: - -```json -{ - "format_version": 1, - "key": "host.version", - "value": "0.1.0" -} -``` - -控制键以 ASCII 字母或数字开头,最长 128 字节,其余只能是字母、数字、点、下划线或连字符。它是信息命名协议,不是文本编码限制;值支持日文、中文、emoji 等 Unicode 文本。用户文本不应被当作文件路径或控制键。 - -文件必须是有效 UTF-8 JSON,版本和 key 必须匹配请求。损坏记录返回错误,不清空、不用默认值偷偷替换。文件不属于 VM 堆快照,也不存数据库凭据。 - -## 读写与生命周期 - -CLI 的 metadata put/get 通过 host::MetadataStore 访问文件,不连接 SQL: - -```sh -cli metadata put project.note '日本語・中文・😀' -cli metadata get project.note -cli metadata get host.version -``` - -MetadataStore::clone 只共享目录路径,没有缓存;每次读取重新打开文件。写入先建立同目录唯一临时文件,写完并 sync_all,再通过 rename 发布;Unix 新文件权限 0600。不同键分别写文件,不会因整张 map 的读改写丢失其他键;同键并发是最后一次成功发布生效。 - -没有跨键事务、分布式锁或共享网络文件系统一致性保证;文件同步与原子发布也不等于数据库的完整断电持久性协议。异常进程终止可能留下隐藏临时文件,正常失败会清理自己的临时文件。 - -## 旧基础信息的一次性迁移 - -ces-control 发布时,在 host/.legacy-imported 标记缺失的情况下,只读检查 PostgreSQL 原 public.aster_metadata 表。存在时,使用 UTF-8 客户端导出 key/value JSON,再经 cli metadata import 的标准输入导入。该命令只创建缺失文件,不覆盖 Server 刚写入的版本或其他已有文件;临时导出文件以私有权限创建并清理,不打印数据内容。 - -只导入缺失项通过同目录临时文件的原子 hard_link 实现,因此运行目录应使用支持该操作的本地文件系统。导入可重复执行;只有导入成功或源表不存在才写完成标记。损坏记录、不支持的控制键或 I/O 错误会停止发布,保留原库数据,不能当作迁移成功。 - -本迁移针对已配置的 PostgreSQL 生产路径,不自动扫描或删除其他 SQLite 文件。原数据库表与数据不会被修改/删除。此处说明发布脚本行为,不代表远端已执行成功;按约定不主动跟踪 CI 或服务器。 - -部署记录仍以版本镜像、release.env 与 current 链接为发布来源;deployment.release.json 是文件信息记录,不是数据库迁移表。脚本见 [scripts](../../scripts/README.md)。 - -返回 [运行指南](README.md) 或 [文档索引](../README.md)。 diff --git a/docs/operations/validation.md b/docs/operations/validation.md index a318a80..549c473 100644 --- a/docs/operations/validation.md +++ b/docs/operations/validation.md @@ -1,62 +1,13 @@ -# 验证范围与结果边界 +# 引擎验证 -本页维护当前检查入口和覆盖范围,不累计已失效的测试数量、机器资源快照或旧对象模型的通过记录。历史行为查 Git 历史;某个版本是否通过,只能依据该提交对应的实际结果。 +本地不运行 Cargo 构建、测试、Clippy、冒烟、Docker 或依赖下载。允许静态阅读、格式化、ASDL 生成/同步检查及文档链接检查;运行验证交给 ces-control。不主动查询 CI/部署结果。 -## 执行约定 +[ci.yml](../../.gitea/workflows/ci.yml) 在 main push、pull request 和手动触发时运行引擎 CI,标签为 ces-control。Dockerfile.ci 只提供远端 Rust 1.91.1 验证环境,不生成生产服务镜像或执行部署。 -本地不运行 Cargo 构建、测试、Clippy、冒烟、Docker 或依赖下载;允许阅读、格式化、ASDL 源码生成/产物同步检查和静态文件/文档检查。指定运行验证环境为 ces-control。按用户要求不主动追踪 CI/部署结果,有问题由用户反馈后再查。 +远端的 scripts/check.sh 验证 ASDL 产物、生成器回归、Cargo 格式、独立 PEG 文件格式、cargo test --workspace --locked 和 Clippy;包括 objects/core 的对象与执行测试、frontend 的读取/AST/语法测试、compiler 的名称分析/源码执行测试及 rustdoc。Compiler 的五个源码样例继续位于 crates/compiler/examples。 -## 检查入口 +五个样例的真实命令行输出报告改由 [Lyra CI](https://gitea.entroia.com/Zayden-Jung/endorphin-lyra-cli) 调用真实 lyra 可执行文件生成,保存源码、预期/实际 stdout、stderr 和退出码;失败也保留 artifact。Aster 的编译器测试与 Lyra 的可执行程序测试是不同层次,消费者的 cargo test 不会自动运行依赖库的全部自身测试。 -以下命令只在指定的验证环境执行,不表示已经运行: +HTTP、宿主 JSON、SQLite 与真实 PostgreSQL 测试归 [Hestia CI](https://gitea.entroia.com/Zayden-Jung/endorphin-hestia-server)。Aster 无 CD;现有服务和数据由 Hestia 的独立部署定义保留。CI 的 bot SSH 凭据要求见 [.gitea](../../.gitea/README.md)。源码和工作流定义不证明运行通过;未查询时结果记为未核实。 -```sh -sh scripts/check.sh -cargo test -p storage --test postgres --locked -- --ignored -python3 scripts/smoke.py -``` - -check.sh 先运行 generate_ast.py --check 验证 ASDL 产物同步,以及仅远端执行的 test_generate_ast.py 生成器回归检查、test_endor_examples.py 报告失败路径检查,再执行 cargo fmt --all --check、独立词法/语法文件的 rustfmt --edition 2024 --check crates/frontend/src/lexer.peg crates/frontend/src/grammar.peg、cargo test --workspace --locked 和 cargo clippy --workspace --all-targets --locked -- -D warnings。workspace 测试也包含 rustdoc 示例;显式忽略的 PostgreSQL 测试需要单独执行。 - -ci-ces-control.sh 负责准备全新专用临时 PostgreSQL 和测试 URL;成功路径使用真实数据库,不用模拟 socket 代替。测试库与生产数据不共享挂载。没有 ASTER_TEST_POSTGRES_URL 时,显式执行 PostgreSQL 测试会失败,不标记为通过。 - -## Endor 示例实际输出 - -在对应 Gitea 仓库的 Actions → 本次提交的 run 页面,打开 Show Endor example execution output 步骤可看实际运行报告;下载 endor-execution-output- artifact 可保存两种格式: - -- endor-examples.md:逐例展示源码、执行命令、预期值、实际 stdout/stderr、退出码和 PASS/FAIL/NOT_RUN。 -- endor-examples.json:保留精确输出(包括换行)、提交 SHA、UTC 时间、构建/定位命令结果和逐例状态,供工具读取。 - -[endor_examples.py](../../scripts/endor_examples.py) 构建真实 CLI,通过 Cargo metadata 定位二进制并执行 [cases.json](../../crates/compiler/examples/cases.json) 中的全部示例;不以预期值冒充实际值。stdout 必须精确匹配、stderr 为空且退出码为 0 才通过。单例超时 30 秒;构建失败不运行示例并标记 NOT_RUN;报告本身的异常标记 FAIL。一个案例失败仍收集后续案例。示例报告不代表 workspace、数据库、宿主和部署全部通过,整体结果仍看工作流。 - -ci-ces-control.sh 在检查容器退出后、清理前将两份文件复制到宿主 ci-output;验证步骤提前失败时已有 NOT_RUN 占位说明。报告运行不掩盖前面检查的失败;后续数据库或宿主检查失败也不丢报告。工作流用 always() 展示并上传,artifact 保留 30 天,不写进 Git 或生产数据目录。普通 Actions 日志仍保留完整测试/构建信息;报告上传失败会使工作流失败。 - -## 覆盖范围与文件位置 - -| 范围 | 当前检查内容 | 入口 | -| --- | --- | --- | -| 堆内部边界 | 非法索引、槽位代数、代数耗尽、计数溢出原子性 | [objects/src](../../crates/objects/src/README.md) 的 heap.rs 私有单元测试 | -| 独立对象存储 | 不依赖 VM 的稳定地址、跨存储拒绝、更新失败原子性、循环回收、函数校验与共享代码所有权 | [objects/tests](../../crates/objects/tests/README.md) 的 store.rs | -| 对象运行时 | 地址稳定、引用/借用、属性、长链、循环、原生操作、VM 销毁 | [core/tests](../../crates/core/tests/README.md) 的 runtime.rs | -| 基本对象 | Number 表示、字符串、Dict/Cell 引用与更新、函数环境、异常数据 | 同目录 builtins.rs | -| 字节码 | 编解码、非法指令/控制流、跳转、调用、闭包、字节码创建函数/默认值/环境捕获、函数创建及后续失败清理、结果存活与限额 | 同目录 execution.rs | -| Endor 阅读器 | 字符串、分块 UTF-8 流、文件、读取失败、URI provider、修订标签与来源诊断 | [frontend/tests](../../crates/frontend/tests/README.md) 的 reader.rs | -| AST 与接口生成 | 三类根、声明/语句层次、Visitor 及失败退出、Unicode 位置、JSON 往返、schema/范围错误和跨语言产物标识 | frontend/tests/ast.rs 与 scripts/test_generate_ast.py,见 [AST 模型](../runtime/ast.md) | -| 源码前端 | 字符 PEG 词法边界、逐 Token 原文位置与首个错误诊断、Unicode 范围、数字原文、字符串与错误、优先级/结合性、函数/绑定、完整输入与错误位置、源码样例 | [frontend/tests](../../crates/frontend/tests/README.md) | -| 源码编译与执行 | 真实 Endor 文件到 VM、名称/初始化检查、参数与函数值、精确常量、宿主成员回调、跨 VM 模板、错误清理、递归限额、交互命名空间及扩展操作数 | [compiler/tests](../../crates/compiler/tests/README.md) | -| CLI 执行 | run/eval 文件与 stdin、入口、返回值、语义/编译/运行错误、traceback 和退出码 | [cli/tests](../../crates/cli/tests/README.md) | -| CLI 解析 | AST 输出、未执行源码、文件/stdin 来源和行列错误、EOF 读取与非法 UTF-8 退出 | [cli/tests](../../crates/cli/tests/README.md) | -| 公开示例 | 对象操作、手写指令执行与释放 | objects 与 core 的 src/lib.rs rustdoc,见 [objects/src](../../crates/objects/src/README.md)、[core/src](../../crates/core/src/README.md) | -| 宿主文件信息 | UTF-8 值/路径、持久读取、同键替换、并发、坏记录、幂等迁移与保留新值 | [host/tests](../../crates/host/tests/README.md) | -| 数据库连接 | 不自动建表、显式配置、隔离/共享、调用方数据重连、失败不降级、真实 PostgreSQL | [storage/tests](../../crates/storage/tests/README.md) | -| 端到端宿主 | 文件 metadata 跨进程读写、日文值、Server 文件版本;忽略失效数据库变量且不创建 SQLite 文件、HTTP 与 SIGTERM | [scripts](../../scripts/README.md) 的 smoke.py | - -测试源码说明验证意图,不能证明已通过;现有测试也不代表完整 Python 兼容、panic 恢复、云沙箱或 VM 快照已经实现。 - -## 文档修改的静态检查 - -修改后核对所有受版本管理目录存在 README、父目录有入口、相对链接指向现存文件;运行 git diff --check 检查差异空白。静态文本检索可以查出旧路径和过时 API;维护规则见 [开发与文档维护](../contributing/README.md)。这些检查不执行 VM 或数据库。 - -仅修改文档时也不据此宣称运行检查通过。推送触发现有 CI 时,仍由原流程检查和部署;报告应分别说明静态检查、运行测试与推送结果。 - -返回 [运行指南](README.md) 或 [文档索引](../README.md)。 +返回 [使用引擎](README.md)。 diff --git a/docs/runtime/README.md b/docs/runtime/README.md index 1c25c1d..fd55ac0 100644 --- a/docs/runtime/README.md +++ b/docs/runtime/README.md @@ -1,18 +1,15 @@ -# 运行时设计 +# 运行时契约 -本目录解释源码输入、前端及跨越 core/objects 的运行时契约。对象与存储定位见 [objects/src](../../crates/objects/src/README.md),VM 与执行帧见 [core/src](../../crates/core/src/README.md);检查分别见 [objects/tests](../../crates/objects/tests/README.md) 和 [core/tests](../../crates/core/tests/README.md)。 - -| 主题 | 内容 | +| 文档 | 内容 | | --- | --- | -| [对象模型](objects.md) | EndorObject/ObjectHeader、代际句柄、操作协议、托管引用与循环回收 | -| [基本对象与 Number](builtins.md) | 函数、原生函数、字典、Cell、异常数据、字符串与无损数字 | -| [Endor 阅读器](reader.md) | SourceUnit、来源/修订标签、字符串/流/文件和宿主 URI provider | -| [源码前端](frontend.md) | 字符 PEG lexer、token PEG parser、AST、基础函数/表达式语法与诊断 | -| [AST 模型与 ASDL](ast.md) | 统一根、声明/语句/表达式、生成器、Visitor、位置和 JSON/编辑器接口 | -| [源码编译](compiler.md) | 名称/作用域、局部槽、字节码模板、代码装载、CLI 执行与示例 | -| [字节码 VM](bytecode.md) | 指令编码、代码校验、局部槽、执行帧、函数创建、调用/返回与边界 | -| [文本编码](text-encoding.md) | UTF-8、日文、外部 Shift_JIS/CP932 与宿主解码边界 | -| [数据库特殊类](database-objects.md) | 显式连接、调用方数据模型、未来类接入与资源生命周期 | -| [生命周期](lifecycle.md) | 宿主持有 Vm、帧执行根、结果所有权、错误退出和整体销毁 | +| [objects.md](objects.md) | 对象、句柄与默认操作 | +| [builtins.md](builtins.md) | Number、String、Dict、Cell、Function、异常数据 | +| [reader.md](reader.md) | 输入、来源和修订标签 | +| [frontend.md](frontend.md) | 词法、语法和诊断 | +| [ast.md](ast.md) | ASDL、AST、生成与编辑器接口 | +| [compiler.md](compiler.md) | 名称分析、编译、装载与源码样例 | +| [bytecode.md](bytecode.md) | 指令、验证和调用协议 | +| [lifecycle.md](lifecycle.md) | 根引用、失败、回收与销毁 | +| [text-encoding.md](text-encoding.md) | UTF-8 和宿主转码边界 | -文本/环境约束和执行限额集中在 [配置](../operations/configuration.md);组件之间的关系见 [系统架构](../architecture/README.md),解释执行与源码/平台版本的设计方向见 [执行与版本模型](../architecture/execution-and-versions.md)。返回 [文档索引](../README.md)。 +文件信息与数据库适配属于独立 Hestia 宿主,不属于引擎;CLI 属于独立 Lyra 宿主。返回 [文档索引](../README.md)。 diff --git a/docs/runtime/ast.md b/docs/runtime/ast.md index 9ab9b67..c000606 100644 --- a/docs/runtime/ast.md +++ b/docs/runtime/ast.md @@ -64,11 +64,11 @@ AST_SCHEMA_ID 从 Endor.asdl 的 UTF-8 文本计算 SHA-256,包含注释与空 CLI 可提供具体的交换输出: ```sh -cli parse --json source.endor -cli parse --mode expression --json - -cli parse --mode interactive --json - +lyra parse --json source.endor +lyra parse --mode expression --json - +lyra parse --mode interactive --json - ``` -文件/stdin 阅读保持原有 UTF-8 和错误来源规则,stdin 到 EOF 后才解析。普通 cli parse 输出 AstRoot 的 Debug 文本,稳定交换使用带 schema 的 JSON;parse 的各模式均不执行源码;run/eval 执行入口见 [编译链路](compiler.md)。命令在指定远端运行。 +文件/stdin 阅读保持原有 UTF-8 和错误来源规则,stdin 到 EOF 后才解析。普通 lyra parse 输出 AstRoot 的 Debug 文本,稳定交换使用带 schema 的 JSON;parse 的各模式均不执行源码;run/eval 执行入口见 [编译链路](compiler.md)。命令在指定远端运行。 检查源码见 [frontend/tests](../../crates/frontend/tests/README.md) 和 [生成器检查](../../scripts/test_generate_ast.py),边界见 [验证](../operations/validation.md)。返回 [运行时索引](README.md)。 diff --git a/docs/runtime/compiler.md b/docs/runtime/compiler.md index bcac6a1..3ed77f7 100644 --- a/docs/runtime/compiler.md +++ b/docs/runtime/compiler.md @@ -34,7 +34,7 @@ CompileOptions.globals 显式声明宿主会提供的 globals/builtins 名称 | 成员读取 | 求值对象 → LoadAttr;沿用 VM 属性协议,无自动 self 绑定 | | return | 求值结果 → ReturnValue;无值 return 或 AST 函数末尾无 return 时生成 nil | -源码 parser 仍要求函数体为 let* 加一个带值 return;更通用的表达式语句和空 return 可以由 AST 调用方构造,但不能因此宣称源码语法已开放。模块初始化只发布函数并返回 nil,不自动调用 main。CLI 选择入口属于宿主行为。 +源码 parser 仍要求函数体为 let* 加一个带值 return;更通用的表达式语句和空 return 可以由 AST 调用方构造,但不能因此宣称源码语法已开放。模块初始化只发布函数并返回 nil,不自动调用 main。Lyra 宿主选择入口属于宿主行为。 二元算术、非字面量的一元负号和符号值 .name 目前产生明确 Compilation 诊断,不伪造运行结果。导入、类、Agent、条件/循环、关键字参数等仍未实现。函数参数个数和对象是否可调用由 VM 在运行时检查。 @@ -44,12 +44,12 @@ CompiledCode 是纯 Rust 模板,嵌套代码常量在模板内部使用占位 宿主为模块提供 globals 字典,调用 vm.execute(code, globals) 发布函数,然后从字典借用函数句柄并调用 vm.call。函数共享装载后的代码,反复调用不重新编译;execute/call 的对象结果持有根,用后 release_value。函数指向 globals,globals 保存函数,因此宿主需保留命名空间期间的根,并在销毁时清空字典或释放根后显式 collect_cycles。编译器不清空宿主字典。 -Expression 模式返回表达式值;Interactive 模式严格编译一次声明、let 或表达式。声明/绑定返回 nil,表达式返回值;宿主可以跨提交复用 globals,并在下一次 CompileOptions 中声明可见名称。CLI 当前没有 REPL、续行或自动会话管理。 +Expression 模式返回表达式值;Interactive 模式严格编译一次声明、let 或表达式。声明/绑定返回 nil,表达式返回值;宿主可以跨提交复用 globals,并在下一次 CompileOptions 中声明可见名称。Lyra 当前没有 REPL、续行或自动会话管理。 代码对象保存来源字符串、函数名和定义起始行,现有 VM traceback 保存该位置及指令偏移;没有每条指令对应的精确源码行/列映射。现有 ExecutionLimits 帧数/指令数限制可由嵌入方继续使用;本链路不构成不可信代码沙箱。 -## CLI 与检查 +## Lyra 宿主与检查 -cli run [--entry name] 编译模块、执行初始化、调用无参数入口(默认 main),输出返回值一行。cli eval 编译执行一个表达式。字符串输出原始内容,数字输出无损表示,对象使用 VM str;失败仅输出 stderr 并返回非零退出码。文件和 stdin 严格 UTF-8,stdin 到 EOF 后才处理。CLI 在成功或失败后释放代码/结果并清空自己创建的命名空间。 +lyra run [--entry name] 编译模块、执行初始化、调用无参数入口(默认 main),输出返回值一行。lyra eval 编译执行一个表达式。字符串输出原始内容,数字输出无损表示,对象使用 VM str;失败仅输出 stderr 并返回非零退出码。文件和 stdin 严格 UTF-8,stdin 到 EOF 后才处理。Lyra 在成功或失败后释放代码/结果并清空自己创建的命名空间。 -可执行文件见 [示例目录](../../crates/compiler/examples/README.md),API/所有权与错误路径见 [pipeline.rs](../../crates/compiler/tests/pipeline.rs),进程入口见 [CLI 检查](../../crates/cli/tests/README.md)。这些检查由既有远端 workspace 测试覆盖;本地只做静态检查,不据此宣称实际执行通过,见 [验证约定](../operations/validation.md)。返回 [运行时索引](README.md)。 +可执行文件见 [示例目录](../../crates/compiler/examples/README.md),API/所有权与错误路径见 [pipeline.rs](../../crates/compiler/tests/pipeline.rs),进程入口见 [CLI 检查](https://gitea.entroia.com/Zayden-Jung/endorphin-lyra-cli/src/branch/main/crates/cli/tests/README.md)。引擎检查由 Aster CI 覆盖,进程入口检查由独立 Lyra CI 覆盖;本地只做静态检查,不据此宣称实际执行通过,见 [验证约定](../operations/validation.md)。返回 [运行时索引](README.md)。 diff --git a/docs/runtime/database-objects.md b/docs/runtime/database-objects.md deleted file mode 100644 index f775e87..0000000 --- a/docs/runtime/database-objects.md +++ /dev/null @@ -1,36 +0,0 @@ -# 数据库特殊类与连接边界 - -数据库连接用于某类明确需要数据库的特殊对象,不用于 VM 版本、部署版本、普通对象信息或统一 key/value 元数据。当前尚未定义该特殊类的名字、载荷布局或语言接口,本页说明已保留的连接能力及接入边界。 - -## 当前分层 - -- Objects 负责核心对象与私有堆,Core 负责 VM 和执行;两者不依赖 SQLx 或数据库。 -- Storage 仅暴露 DatabaseConfig、DatabaseConnection 和 DatabaseError。 -- CLI/Server 不依赖 Storage,启动、bootstrap 和 health 不访问数据库;基础信息由独立 host 文件层处理。 -- 特殊类以后由宿主实现按需取得数据库连接;尚未自动挂入 Vm 或对象工厂。 - -## 显式建立连接 - -调用方先构造 DatabaseConfig::Postgres(url)、SqliteFile(path) 或 SqliteMemory,再 await DatabaseConnection::connect。没有 from_env、默认 SQLite 路径或应用启动时的全局数据库。 - -connect 会建立真实连接,并可在显式请求 SQLite 文件时创建父目录和文件,但不创建任何应用表。SQLite WAL、连接协商等驱动自身操作不等于应用 schema 初始化。不会创建基础信息表,也不会写入运行或发布信息。 - -sqlite_pool/postgres_pool 返回匹配后端的 SQLx 池借用,另一后端返回 None。特殊类以后可在自己的适配实现里执行参数化查询、事务及由该类定义的数据结构;本层不预设通用 KV、ORM、迁移或序列化协议。现有 SQLx API 见 [Pool 文档](https://docs.rs/sqlx/0.8.6/sqlx/struct.Pool.html)。 - -## 生命周期 - -DatabaseConnection::clone 共享连接池;close().await 显式关闭所有克隆使用的池,并等待已借出连接归还。取出的 SQLx 池还能被调用方克隆,因此单个包装值销毁不保证所有使用者立即结束。 - -未来将连接放进原生载荷时,必须另行定义异步关闭、共享所有权和错误传播;当前 VM 的同步 Drop/Native 回调不等于已经完成异步数据库类。连接跨进程不能复用,也不代表 VM 快照。 - -## 既有数据和部署 - -原先的基础信息存储已从代码中移除。已有数据库表、卷、密码和服务器数据未执行删除或迁移;它们是否存在、有哪些记录不由本次代码修改推断。 - -生产继续保留预置 PostgreSQL 服务与既有数据目录,等待实际类的消费接口。应用容器不注入数据库连接变量,当前不消费该服务。部署版本通过镜像标签、release.env 和 current 链接识别,基础信息写入文件;发布时仅为旧数据做一次只读导入,不继续写数据库。详见 [宿主信息](../operations/host-information.md)。 - -## 验证 - -SQLite 检查先确认新连接没有应用表,再由测试自己创建 class_rows 验证查询和重连。真实 PostgreSQL 使用全新临时测试库,检查连接不建表,并在事务中创建提交后自动删除的临时表。这些是适配层测试,不代表特殊类已实现。 - -文件定位见 [storage](../../crates/storage/README.md),运行与生产资源见 [部署](../operations/deployment.md)。返回 [运行时索引](README.md)。 diff --git a/docs/runtime/frontend.md b/docs/runtime/frontend.md index ee0e1c5..ca482d3 100644 --- a/docs/runtime/frontend.md +++ b/docs/runtime/frontend.md @@ -73,6 +73,6 @@ use、namespace、class、agent、完整类型语法、条件/循环、比较 词法和语法错误分别标记为 Lexical/Syntax。语法错误将 PEG 的 token 下标转换成原始源码 Span,保留期望项目列表;EOF 错误使用 source.len() 处的空区间。Diagnostic::location 返回一基行列,列按 Unicode 标量计数,CRLF 只算一次换行,不按字节列或字形簇计数。 -`cli parse ` 与 `cli parse -` 成功时输出 Rust Debug 格式的 AST,失败时在 stderr 输出来源/行/列和诊断并返回非零退出码;`-` 表示从 stdin 读到 EOF,不是 REPL。阅读与来源包装规则见 [阅读器](reader.md)。可用 --mode 选择 module/expression/interactive,用 --json 输出带 schema 标识的 AstDocument;普通输出为 AstRoot Debug 文本,稳定交换格式见 [AST 模型](ast.md)。命令不执行源码、不初始化 VM、不连接数据库;具体运行与本地禁测约定见 [运行](../operations/README.md) 和 [验证](../operations/validation.md)。 +`lyra parse ` 与 `lyra parse -` 成功时输出 Rust Debug 格式的 AST,失败时在 stderr 输出来源/行/列和诊断并返回非零退出码;`-` 表示从 stdin 读到 EOF,不是 REPL。阅读与来源包装规则见 [阅读器](reader.md)。可用 --mode 选择 module/expression/interactive,用 --json 输出带 schema 标识的 AstDocument;普通输出为 AstRoot Debug 文本,稳定交换格式见 [AST 模型](ast.md)。命令不执行源码、不初始化 VM、不连接数据库;具体运行与本地禁测约定见 [运行](../operations/README.md) 和 [验证](../operations/validation.md)。 当前 Compiler 将 AST 经模块/函数名称分析转换为现有字节码,再交给 VM 解释执行。这是解释器内部的编译/执行准备步骤,不要求使用者先构建程序,见 [执行与版本模型](../architecture/execution-and-versions.md)。MakeFunction 已接入源码函数编译,但解析接受的二元算术和符号值仍没有执行语义;编译器会给出明确诊断,见 [编译链路](compiler.md)。源码闭包尚未实现。解析器也不是不可信代码沙箱,当前没有通用输入大小或解析递归深度限额。 diff --git a/docs/runtime/lifecycle.md b/docs/runtime/lifecycle.md index ece481f..df837b6 100644 --- a/docs/runtime/lifecycle.md +++ b/docs/runtime/lifecycle.md @@ -33,6 +33,6 @@ Rust panic 尚无专门的捕获恢复或执行根 RAII 清理机制。宿主捕 Vm 没有独立后台线程、暂停状态或显式 shutdown 状态机。Rust 所有权销毁它所拥有的堆和剩余载荷,包括没有先做循环回收的对象;原生共享资源继续遵循各自的 Rust 所有权。外部 ObjectRef 不保活 Vm。 -Server 的 Tokio 运行时、监听器、信号处理和 Host 文件信息由宿主管理;优雅退出见 [运行](../operations/README.md)。当前宿主不持有数据库连接。数据库能力只为未来的特殊类按需提供,异步连接关闭与原生载荷生命周期仍需单独接入,见 [数据库特殊类](database-objects.md)。数据库不自动保存或恢复 VM 对象。 +Server 的 Tokio 运行时、监听器、信号处理和 Host 文件信息由宿主管理;优雅退出见 [运行](../operations/README.md)。当前宿主不持有数据库连接。数据库能力只为未来的特殊类按需提供,异步连接关闭与原生载荷生命周期仍需单独接入,见 [数据库特殊类](https://gitea.entroia.com/Zayden-Jung/endorphin-hestia-server/src/branch/main/docs/runtime/database-objects.md)。数据库不自动保存或恢复 VM 对象。 源码定位:[objects/src](../../crates/objects/src/README.md) 中的 store.rs/heap.rs 负责对象生命周期,[core/src](../../crates/core/src/README.md) 中的 vm.rs/frame.rs/eval.rs 负责执行状态和帧根。返回 [运行时索引](README.md)。 diff --git a/docs/runtime/objects.md b/docs/runtime/objects.md index 031395c..1cfd50d 100644 --- a/docs/runtime/objects.md +++ b/docs/runtime/objects.md @@ -102,6 +102,6 @@ compare 的反向规则是 Lt 对应 Gt、Le 对应 Ge,Eq/Ne 保持不变。 已实现:统一对象结构、稳定地址、代际句柄、身份、显式引用计数、托管属性、原生操作覆盖、比较与哈希、原生调用、资源清理和显式循环回收。 -尚未实现:Python type 系统、语言根类、继承和描述符、可变 VM 调用上下文、自动/分代/增量 GC、语言终结器/复活、完整资源限额和 VM 快照;独立 frontend 已提供阅读器、lexer/parser 与 AST,基础源码执行由独立 [Compiler](compiler.md) 接入,见 [源码前端](frontend.md);执行器已有帧数和指令数限额。数据库仅为未来特殊类提供按需连接,普通对象或宿主基础信息不自动持久化,详见 [数据库特殊类](database-objects.md)。 +尚未实现:Python type 系统、语言根类、继承和描述符、可变 VM 调用上下文、自动/分代/增量 GC、语言终结器/复活、完整资源限额和 VM 快照;独立 frontend 已提供阅读器、lexer/parser 与 AST,基础源码执行由独立 [Compiler](compiler.md) 接入,见 [源码前端](frontend.md);执行器已有帧数和指令数限额。数据库仅为未来特殊类提供按需连接,普通对象或宿主基础信息不自动持久化,详见 [数据库特殊类](https://gitea.entroia.com/Zayden-Jung/endorphin-hestia-server/src/branch/main/docs/runtime/database-objects.md)。 验证范围与执行约定见 [验证](../operations/validation.md)。 diff --git a/docs/runtime/reader.md b/docs/runtime/reader.md index 3ce8e57..6f3ff14 100644 --- a/docs/runtime/reader.md +++ b/docs/runtime/reader.md @@ -42,7 +42,7 @@ revision 是宿主返回的原样标签;Reader 只拒绝全空白标签,不 ## CLI 与交互输入 -CLI parse 文件通过 from_file → parse_source 输出 AST;`cli parse -` 通过 from_stream 读取 stdin 到 EOF,来源为 ``。错误保留文件或流名及行列;文件读取与非法 UTF-8 错误在进入 lexer 前返回。parse 不执行源码;run/eval 使用相同阅读器后进入 [Compiler](compiler.md),支持范围见该契约。 +CLI parse 文件通过 from_file → parse_source 输出 AST;`lyra parse -` 通过 from_stream 读取 stdin 到 EOF,来源为 ``。错误保留文件或流名及行列;文件读取与非法 UTF-8 错误在进入 lexer 前返回。parse 不执行源码;run/eval 使用相同阅读器后进入 [Compiler](compiler.md),支持范围见该契约。 stdin 入口不是 REPL,不打印提示符、不保持求值环境,也不区分“需要继续输入”和“真正语法错误”。未来 REPL 宿主先收集一次提交的输入单元,再使用 from_text 并指定会话来源;reader 不承担交互调度。包与 namespace 的成员查找和依赖解析也不由 reader 自动完成。 diff --git a/docs/runtime/text-encoding.md b/docs/runtime/text-encoding.md index f98e099..863f902 100644 --- a/docs/runtime/text-encoding.md +++ b/docs/runtime/text-encoding.md @@ -26,4 +26,4 @@ EndorReader 提供严格 UTF-8 文件/流读取和宿主 URI provider 适配 当前按 Unicode 标量值访问字符,不按人眼字形簇。预组合的“ガ”和“カ”加组合浊点可显示相似,但标量数量和序列不同;当前没有自动规范化或全角/半角转换。字符串和字典按当前内容语义处理,不为了所在地悄悄改写数据。前端当前采用的名称字符分类与源码位置规则见 [源码前端](frontend.md),名称同样不自动规范化。 -宿主基础信息以 UTF-8 JSON 写入;控制键的 ASCII 命名限制属于文件信息协议,值可保存日文,详见 [宿主信息](../operations/host-information.md)。配置与 locale 见 [配置](../operations/configuration.md),返回 [运行时索引](README.md)。 +宿主基础信息以 UTF-8 JSON 写入;控制键的 ASCII 命名限制属于文件信息协议,值可保存日文,详见 [宿主信息](https://gitea.entroia.com/Zayden-Jung/endorphin-hestia-server/src/branch/main/docs/operations/host-information.md)。配置与 locale 见 [配置](../operations/configuration.md),返回 [运行时索引](README.md)。 diff --git a/scripts/README.md b/scripts/README.md index c09425c..a2726a7 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,18 +1,10 @@ -# 检查与发布脚本 +# 引擎维护脚本 -所有构建、测试、冒烟和部署脚本只在用户指定的远端环境运行。generate_ast.py 是源码生成/静态同步工具,可本地使用;test_generate_ast.py 仅远端执行。本目录 README 负责定位与副作用边界;全局流程见 [验证](../docs/operations/validation.md) 和 [部署](../docs/operations/deployment.md)。 - -| 文件 | 入口与副作用 | +| 文件 | 职责 | | --- | --- | -| [generate_ast.py](generate_ast.py) | 从 Endor.asdl 生成并格式化 Rust/TypeScript/JSON Schema;--write 更新产物,--check 只比较,无依赖下载或语言执行 | -| [test_generate_ast.py](test_generate_ast.py) | 远端 unittest:schema 扩展同步、递归装箱/遍历、非法定义拒绝 | -| [endor_examples.py](endor_examples.py) | 仅远端:构建 CLI,按 examples/cases.json 执行每个真实 Endor 文件,严格比较 stdout/stderr/退出码;保存源码、命令、实际输出、PASS/FAIL/NOT_RUN 的 Markdown 和 JSON,失败也写报告 | -| [test_endor_examples.py](test_endor_examples.py) | 仅远端:报告清单覆盖、精确输出、不匹配、构建失败、超时部分输出与异常时仍保存双格式报告;模拟子进程,不启动 VM | -| [check.sh](check.sh) | 先检查 ASDL 产物同步、生成器与输出报告行为,再运行 Cargo fmt 与独立 lexer.peg/grammar.peg 的 rustfmt 检查、workspace 测试、严格 Clippy;会使用 Cargo 依赖和构建产物 | -| [smoke.py](smoke.py) | 标准库 Python 脚本;构建后用 Cargo metadata 定位 cli/server 的 debug 二进制,支持 CARGO_TARGET_DIR;文件 metadata 跨进程与日文值;失效数据库变量下独立启动、不创建 SQLite 文件、HTTP 与 SIGTERM,不改生产配置 | -| [ci-ces-control.sh](ci-ces-control.sh) | 需要 ASTER_RELEASE_SHA;构建 ci 镜像、创建临时网络/PostgreSQL/检查容器;检查后独立执行 Endor 示例,失败不掩盖原检查状态;清理容器前复制两份报告到宿主 ci-output,提前失败也保留 NOT_RUN 说明;退出清理临时容器和网络,保留专属远端 Cargo 缓存卷 | -| [deploy-ces-control.sh](deploy-ces-control.sh) | ces-control root 执行;校验已构建镜像、锁定发布、保留密码和数据、验证 HTTP 与无数据库 CLI 引导后切换 current,失败尝试恢复旧版;保留镜像;应用服务 aster 内调用 cli bootstrap,基础信息改写文件;一次性只读导入旧基础信息,不覆盖新文件或删除原库 | +| README.md | 本目录入口 | +| generate_ast.py | 从 Endor.asdl 生成 Rust/TypeScript/JSON Schema;--check 静态核对 | +| test_generate_ast.py | 生成器回归,仅远端运行 | +| check.sh | 远端格式、生成器、引擎测试和 Clippy | -CI/部署使用的镜像与路径仅属于本项目,禁止改动其他项目和共享 Runner。smoke.py 临时选择端口后再启动宿主,存在小的端口竞争窗口,指定环境遇到冲突可重试。 - -调整变量同步 [配置](../docs/operations/configuration.md),调整端点/命令同步 [运行指南](../docs/operations/README.md),调整脚本名同步 [工作流](../.gitea/workflows/README.md)。返回 [项目入口](../README.md)。 +CLI 示例实际输出脚本已归 Lyra;数据库、HTTP 冒烟与部署脚本已归 Hestia。Aster 无生产部署。返回 [项目入口](../README.md)。 diff --git a/scripts/check.sh b/scripts/check.sh index 32ac8a7..2a5e793 100644 --- a/scripts/check.sh +++ b/scripts/check.sh @@ -2,7 +2,6 @@ set -eu python3 scripts/generate_ast.py --check python3 scripts/test_generate_ast.py -python3 scripts/test_endor_examples.py cargo fmt --all --check rustfmt --edition 2024 --check crates/frontend/src/lexer.peg crates/frontend/src/grammar.peg cargo test --workspace --locked diff --git a/scripts/ci-ces-control.sh b/scripts/ci-ces-control.sh deleted file mode 100644 index eeb5273..0000000 --- a/scripts/ci-ces-control.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/bin/sh -# Run only on the designated remote CI host, never on the developer's machine. -set -eu -cd "$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" - -release_sha=${ASTER_RELEASE_SHA:?ASTER_RELEASE_SHA is required} -case "$release_sha" in *[!0-9a-f]*|'') echo "Invalid release SHA" >&2; exit 1;; esac -test "${#release_sha}" -eq 40 - -ci_id="${GITHUB_RUN_ID:-manual}-$$" -network="endorphin-aster-vm-ci-$ci_id" -postgres="endorphin-aster-vm-ci-pg-$ci_id" -checks="endorphin-aster-vm-ci-checks-$ci_id" -image="endorphin-aster-vm-ci:$release_sha" -report_dir="$PWD/ci-output" -mkdir -p "$report_dir" -# Reset both outputs before any build, so even an early failure has an honest -# NOT_RUN artifact rather than a previous attempt's example results. -printf '# Endor 示例实际运行结果\n\n提交:%s\n\n状态:NOT_RUN\n\nCI 尚未完成示例执行;请查看验证步骤日志。\n' "$release_sha" > "$report_dir/endor-examples.md" -printf '{"format_version":1,"commit":"%s","status":"NOT_RUN","error":"CI did not finish example execution; inspect the verification log.","cases":[]}\n' "$release_sha" > "$report_dir/endor-examples.json" -cleanup() { - docker rm -f "$checks" "$postgres" >/dev/null 2>&1 || true - docker network rm "$network" >/dev/null 2>&1 || true -} -trap cleanup EXIT -trap 'exit 130' INT -trap 'exit 143' TERM - -docker build --target ci -t "$image" . -docker network create "$network" >/dev/null -# Disposable test database: no production mounts and no published ports. -docker run -d --name "$postgres" --network "$network" --network-alias postgres \ - --env POSTGRES_USER=aster --env POSTGRES_DB=aster \ - --env POSTGRES_PASSWORD=aster-ci-only \ - --tmpfs /var/lib/postgresql/data postgres:17-bookworm >/dev/null - -ready=false -for attempt in $(seq 1 60); do - if docker exec "$postgres" pg_isready -h 127.0.0.1 -U aster -d aster >/dev/null 2>&1; then - ready=true - break - fi - sleep 1 -done -if [ "$ready" != true ]; then - echo "CI PostgreSQL did not become ready" >&2 - exit 1 -fi - -check_status=0 -docker run --name "$checks" --init --network "$network" \ - --mount type=volume,src=endorphin-aster-vm-ci-cargo,dst=/usr/local/cargo/registry \ - --mount type=volume,src=endorphin-aster-vm-ci-target,dst=/build/target \ - --env ASTER_TEST_POSTGRES_URL='postgres://aster:aster-ci-only@postgres:5432/aster?sslmode=disable' \ - --env ASTER_RELEASE_SHA="$release_sha" \ - "$image" sh -eu -c ' - checks=0 - sh scripts/check.sh || checks=$? - examples=0 - python3 scripts/endor_examples.py --output-dir /build/ci-output || examples=$? - test "$checks" -eq 0 - test "$examples" -eq 0 - cargo test -p storage --test postgres --locked -- --ignored - python3 scripts/smoke.py - ' || check_status=$? - -# Copy reports before the EXIT trap removes the stopped checks container. -# Keep the original validation failure status if report extraction also fails. -copy_status=0 -docker cp "$checks:/build/ci-output/endor-examples.md" "$report_dir/endor-examples.md" || copy_status=$? -docker cp "$checks:/build/ci-output/endor-examples.json" "$report_dir/endor-examples.json" || copy_status=$? -if [ "$check_status" -ne 0 ]; then - exit "$check_status" -fi -exit "$copy_status" diff --git a/scripts/deploy-ces-control.sh b/scripts/deploy-ces-control.sh deleted file mode 100644 index 046d890..0000000 --- a/scripts/deploy-ces-control.sh +++ /dev/null @@ -1,119 +0,0 @@ -#!/bin/sh -# Deploy only the image produced after the remote database checks succeed. -set -eu -cd "$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)" -test "$(id -u)" -eq 0 || { echo "Deployment requires root on ces-control" >&2; exit 1; } -release_sha=${ASTER_RELEASE_SHA:?ASTER_RELEASE_SHA is required} -case "$release_sha" in *[!0-9a-f]*|'') echo "Invalid release SHA" >&2; exit 1;; esac -test "${#release_sha}" -eq 40 -image="endorphin-aster-vm:$release_sha" -docker image inspect "$image" >/dev/null - -umask 077 -root=/opt/endorphin-aster-vm -config=/etc/endorphin-aster-vm/production.env -release="$root/releases/$release_sha" -mkdir -p "$release" /etc/endorphin-aster-vm /var/lib/endorphin-aster-vm/postgres /var/lib/endorphin-aster-vm/host -chown 10001:10001 /var/lib/endorphin-aster-vm/host -chmod 700 /var/lib/endorphin-aster-vm/host -exec 9>/var/lock/endorphin-aster-vm-deploy.lock -flock 9 - -if [ ! -f "$config" ]; then - password=$(openssl rand -hex 24) - printf 'POSTGRES_PASSWORD=%s\nASTER_PORT=18081\n' "$password" > "$config" - unset password -fi -chmod 600 "$config" -cp docker-compose.ces-control.yml "$release/compose.yml" -printf 'ASTER_IMAGE=%s\n' "$image" > "$release/release.env" -previous=$(readlink -f "$root/current" 2>/dev/null || true) -legacy_export= -cleanup_export() { - if [ -n "$legacy_export" ]; then rm -f "$legacy_export"; fi -} -trap cleanup_export EXIT - -compose_at() { - location=$1 - shift - docker compose -p endorphin-aster-vm --env-file "$config" \ - --env-file "$location/release.env" -f "$location/compose.yml" "$@" -} -rollback() { - echo "Deployment failed; application data has been retained" >&2 - if [ -n "$previous" ] && [ "$previous" != "$release" ] && [ -f "$previous/compose.yml" ]; then - echo "Restoring the previously healthy release" >&2 - compose_at "$previous" up -d --no-build --wait --wait-timeout 180 || true - fi -} - -if ! compose_at "$release" up -d --no-build --wait --wait-timeout 180; then - rollback - exit 1 -fi -if ! health=$(compose_at "$release" exec -T aster curl --fail --silent http://127.0.0.1:8080/health); then - rollback - exit 1 -fi -printf '%s' "$health" | python3 -c 'import json,sys; d=json.load(sys.stdin); assert d == {"status":"ok"}' || { - rollback - exit 1 -} -if ! bootstrap=$(compose_at "$release" exec -T aster cli bootstrap); then - rollback - exit 1 -fi -printf '%s' "$bootstrap" | python3 -c 'import sys; s=sys.stdin.read(); assert "runtime=EndorObject type_system=disabled" in s; assert "released heap_objects=0" in s' || { - rollback - exit 1 -} -# Import legacy basic information once, without changing database records. -# Files are authoritative; cli import only creates records that are missing. -if [ ! -f /var/lib/endorphin-aster-vm/host/.legacy-imported ]; then - if ! has_legacy=$(compose_at "$release" exec -T postgres env PGCLIENTENCODING=UTF8 psql -X -U aster -d aster -A -t -v ON_ERROR_STOP=1 \ - -c "SELECT to_regclass('public.aster_metadata') IS NOT NULL"); then - rollback - exit 1 - fi - if [ "$has_legacy" = t ]; then - legacy_export=$(mktemp) - if ! compose_at "$release" exec -T postgres env PGCLIENTENCODING=UTF8 psql -X -U aster -d aster -A -t -v ON_ERROR_STOP=1 \ - -c "SELECT COALESCE(json_agg(json_build_object('key', key, 'value', value)), '[]'::json) FROM public.aster_metadata" > "$legacy_export"; then - rollback - exit 1 - fi - if ! compose_at "$release" exec -T aster cli metadata import < "$legacy_export"; then - rollback - exit 1 - fi - rm -f "$legacy_export" - legacy_export= - elif [ "$has_legacy" != f ]; then - echo "Cannot determine legacy information schema" >&2 - rollback - exit 1 - fi - if ! touch /var/lib/endorphin-aster-vm/host/.legacy-imported; then - rollback - exit 1 - fi -fi -# File-backed host information; no SQL table or database write. -if ! compose_at "$release" exec -T aster cli metadata put deployment.release "$release_sha"; then - rollback - exit 1 -fi -if ! actual=$(compose_at "$release" exec -T aster cli metadata get deployment.release); then - rollback - exit 1 -fi -if [ "$actual" != "$release_sha" ]; then - rollback - exit 1 -fi -ln -sfn "$release" "$root/current.next" -mv -Tf "$root/current.next" "$root/current" -compose_at "$release" ps -echo "Deployed release=$release_sha postgres=provisioned endpoint=127.0.0.1:18081" -echo "Persistent PostgreSQL data: /var/lib/endorphin-aster-vm/postgres" diff --git a/scripts/endor_examples.py b/scripts/endor_examples.py deleted file mode 100644 index cea42e6..0000000 --- a/scripts/endor_examples.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Run Endor example files and retain actual output on the designated CI host. - -Never run locally: this builds the CLI and executes language code. -""" - -import argparse -from datetime import datetime, timezone -import json -import os -from pathlib import Path -import re -import shlex -import subprocess - - -ROOT = Path(__file__).resolve().parents[1] -EXAMPLES = ROOT / "crates/compiler/examples" - - -def decoded(value): - return value.decode("utf-8", "replace") if isinstance(value, bytes) else value or "" - - -def invoke(command, timeout): - """Keep stdout, stderr and failure state even for launch errors/timeouts.""" - record = {"command": command, "exit_code": None, "stdout": "", "stderr": "", "error": None} - try: - result = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, - encoding="utf-8", errors="replace", timeout=timeout) - record.update(exit_code=result.returncode, stdout=result.stdout, stderr=result.stderr) - except subprocess.TimeoutExpired as error: - record.update(stdout=decoded(error.stdout), stderr=decoded(error.stderr), - error=f"timed out after {timeout} seconds") - except OSError as error: - record["error"] = str(error) - return record - - -def load_cases(): - manifest = json.loads((EXAMPLES / "cases.json").read_text(encoding="utf-8")) - if manifest.get("format_version") != 1: - raise ValueError("unsupported example manifest version") - cases = manifest["cases"] - names, sources = set(), set() - for case in cases: - if not re.fullmatch(r"[a-z][a-z0-9_-]*", case["name"]) or case["name"] in names: - raise ValueError("invalid or duplicate example name") - source = EXAMPLES / case["source"] - if source.resolve().parent != EXAMPLES.resolve() or source.suffix != ".endor": - raise ValueError("example source must be a direct .endor file") - if case["mode"] not in ("run", "eval") or not isinstance(case["expected_stdout"], str): - raise ValueError("invalid example mode or expected output") - if case["source"] in sources: - raise ValueError("duplicate example source") - case["source_text"] = source.read_text(encoding="utf-8") - names.add(case["name"]) - sources.add(case["source"]) - if not cases or sources != {path.name for path in EXAMPLES.glob("*.endor")}: - raise ValueError("manifest must cover every .endor example exactly once") - return cases - - -def fence(text, language="text"): - # Keep source/output literal even if future examples print Markdown fences. - longest = max((len(match[0]) for match in re.finditer(r"`+", text)), default=0) - delimiter = "`" * max(3, longest + 1) - return f"{delimiter}{language}\n{text}" + ("" if text.endswith("\n") else "\n") + delimiter - - -def markdown(report): - lines = ["# Endor 示例实际运行结果", "", f"提交:`{report['commit']}`", - f"状态:**{report['status']}**", f"生成时间(UTC):{report['generated_at']}", - "", "本报告仅覆盖 Endor 示例;整个 CI/CD 的状态以工作流结果为准。"] - if report.get("error"): - lines += ["", "报告阶段错误:", fence(report["error"])] - for stage in report["preparation"]: - lines += ["", "## 准备:" + shlex.join(stage["command"]), - f"退出码:{stage['exit_code']};错误:{stage['error'] or '无'}"] - if stage["exit_code"] != 0 or stage["error"]: - lines += ["", "stdout:", fence(stage["stdout"]), "", "stderr:", fence(stage["stderr"])] - for case in report["cases"]: - lines += ["", f"## {case['name']} — {case['status']}", "", - "命令:", fence(shlex.join(case["command"]), "sh"), "", "源码:", - fence(case["source_text"], "endor"), "", "预期 stdout:", - fence(case["expected_stdout"]), "", "实际 stdout:", fence(case["stdout"]), - "", "实际 stderr:", fence(case["stderr"]), "", - f"预期退出码:0;实际退出码:{case['exit_code']}", - f"错误:{case['error'] or '无'}"] - return "\n".join(lines) + "\n" - - -def collect(report): - for case in load_cases(): - report["cases"].append({**case, "status": "NOT_RUN", "exit_code": None, - "stdout": "", "stderr": "", "error": None, - "expected_stderr": "", "expected_exit_code": 0, - "command": ["cli", case["mode"], "crates/compiler/examples/" + case["source"]]}) - build = invoke(["cargo", "build", "-p", "cli", "--locked"], timeout=600) - report["preparation"].append(build) - if build["exit_code"] != 0 or build["error"]: - report.update(status="NOT_RUN", error="CLI build failed; examples were not executed.") - return - metadata = invoke(["cargo", "metadata", "--locked", "--no-deps", "--format-version", "1"], timeout=60) - report["preparation"].append(metadata) - if metadata["exit_code"] != 0 or metadata["error"]: - report.update(status="NOT_RUN", error="Cannot locate the compiled CLI.") - return - binary = Path(json.loads(metadata["stdout"])["target_directory"]) / "debug" / "cli" - for case in report["cases"]: - command = [str(binary), *case["command"][1:]] - actual = invoke(command, timeout=30) - case.update({key: actual[key] for key in ["exit_code", "stdout", "stderr", "error"]}) - case["binary"] = str(binary) - case["status"] = "PASS" if ( - case["exit_code"] == case["expected_exit_code"] and not case["error"] - and case["stdout"] == case["expected_stdout"] - and case["stderr"] == case["expected_stderr"] - ) else "FAIL" - report["status"] = "PASS" if all(case["status"] == "PASS" for case in report["cases"]) else "FAIL" - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--output-dir", type=Path, required=True) - args = parser.parse_args() - args.output_dir.mkdir(parents=True, exist_ok=True) - report = {"format_version": 1, "commit": os.environ.get("ASTER_RELEASE_SHA", "unknown"), - "generated_at": datetime.now(timezone.utc).isoformat(), "status": "NOT_RUN", - "error": None, "preparation": [], "cases": []} - try: - collect(report) - except Exception as error: - report.update(status="FAIL", error=str(error)) - # Always persist diagnostics before returning a failing CI exit code. - args.output_dir.joinpath("endor-examples.json").write_text( - json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") - rendered = markdown(report) - args.output_dir.joinpath("endor-examples.md").write_text(rendered, encoding="utf-8") - print(rendered) - return 0 if report["status"] == "PASS" else 1 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/smoke.py b/scripts/smoke.py deleted file mode 100644 index f37f727..0000000 --- a/scripts/smoke.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python3 -"""Remote CI end-to-end smoke test, no external database required.""" -import json -import os -from pathlib import Path -import signal -import socket -import subprocess -import tempfile -import time -import urllib.request - -ROOT = Path(__file__).resolve().parents[1] - - -def run(*args, env): - return subprocess.run(args, cwd=ROOT, env=env, check=True, text=True, capture_output=True).stdout.strip() - - -def main(): - env = os.environ.copy() - run("cargo", "build", "--workspace", "--locked", env=env) - # Cargo may use a configured CARGO_TARGET_DIR; discover the actual binaries. - metadata = json.loads(run("cargo", "metadata", "--no-deps", "--format-version", "1", env=env)) - binary_dir = Path(metadata["target_directory"]) / "debug" - cli = str(binary_dir / "cli") - server = str(binary_dir / "server") - with tempfile.TemporaryDirectory(prefix="aster-smoke-") as directory: - env["ASTER_STATE_DIR"] = str(Path(directory) / "host-info") - env["SQLITE_PATH"] = str(Path(directory) / "nested" / "aster.db") - # Neither executable may use database settings for basic VM information. - env["DATABASE_URL"] = "invalid-database-url" - bootstrap = run(cli, "bootstrap", env=env) - assert "runtime=EndorObject type_system=disabled" in bootstrap, bootstrap - assert "objects_distinct=true heap_objects=2" in bootstrap, bootstrap - assert "released heap_objects=0" in bootstrap, bootstrap - assert "stored (file)" in run(cli, "metadata", "put", "project.note", "日本語・中文・😀", env=env) - assert run(cli, "metadata", "get", "project.note", env=env) == "日本語・中文・😀" - legacy = json.dumps([{"key": "project.note", "value": "old"}, - {"key": "legacy.note", "value": "preserved"}]) - subprocess.run([cli, "metadata", "import"], input=legacy, cwd=ROOT, env=env, - check=True, text=True, capture_output=True) - assert run(cli, "metadata", "get", "project.note", env=env) == "日本語・中文・😀" - assert run(cli, "metadata", "get", "legacy.note", env=env) == "preserved" - assert not Path(env["SQLITE_PATH"]).exists(), "CLI must not open a database" - - with socket.socket() as sock: - sock.bind(("127.0.0.1", 0)) - port = sock.getsockname()[1] - env["ASTER_LISTEN"] = f"127.0.0.1:{port}" - with tempfile.TemporaryFile(mode="w+") as log: - process = subprocess.Popen([server], cwd=ROOT, env=env, stdout=log, stderr=log) - try: - deadline = time.monotonic() + 15 - while True: - try: - with urllib.request.urlopen(f"http://127.0.0.1:{port}/health", timeout=1) as response: - health = json.load(response) - break - except (OSError, ValueError): - if process.poll() is not None or time.monotonic() >= deadline: - log.seek(0) - raise AssertionError(f"server did not become healthy: {log.read()}") - time.sleep(0.1) - assert health == {"status": "ok"}, health - for _ in range(2): - with urllib.request.urlopen(f"http://127.0.0.1:{port}/bootstrap", timeout=2) as response: - info = json.load(response) - assert info == {"object_model": "EndorObject", "type_system": False, "heap_objects": 0}, info - finally: - process.send_signal(signal.SIGTERM) - try: - process.wait(timeout=10) - except subprocess.TimeoutExpired: - process.kill() - process.wait() - raise AssertionError("server did not shut down gracefully") - assert process.returncode == 0, process.returncode - assert not Path(env["SQLITE_PATH"]).exists(), "Server must not open a database" - assert run(cli, "metadata", "get", "host.version", env=env) == "0.1.0" - record = json.loads((Path(env["ASTER_STATE_DIR"]) / "host.version.json").read_text(encoding="utf-8")) - assert record == {"format_version": 1, "key": "host.version", "value": "0.1.0"} - assert run(cli, "metadata", "get", "project.note", env=env) == "日本語・中文・😀" - print("PASS: no implicit database access; file metadata persistence; HTTP health/bootstrap; SIGTERM shutdown") - - -if __name__ == "__main__": - main() diff --git a/scripts/test_endor_examples.py b/scripts/test_endor_examples.py deleted file mode 100644 index 7b08bc3..0000000 --- a/scripts/test_endor_examples.py +++ /dev/null @@ -1,80 +0,0 @@ -"""Remote-only regression checks for example output capture and failure reports.""" - -import contextlib -import io -import json -from pathlib import Path -import subprocess -import tempfile -import unittest -from unittest.mock import patch - -import endor_examples as examples - - -def result(stdout="", code=0): - return {"command": ["mock"], "exit_code": code, "stdout": stdout, "stderr": "", "error": None} - - -def report(): - return {"format_version": 1, "commit": "test", "generated_at": "test", "status": "NOT_RUN", - "preparation": [], "cases": [], "error": None} - - -class ExampleReportTests(unittest.TestCase): - def test_manifest_runs_every_source_and_captures_exact_output(self): - cases = examples.load_cases() - responses = [result(), result(json.dumps({"target_directory": "/tmp/ci-target"}))] - responses += [result(case["expected_stdout"]) for case in cases] - collected = report() - with patch.object(examples, "invoke", side_effect=responses) as invoke: - examples.collect(collected) - self.assertEqual(collected["status"], "PASS") - self.assertEqual(invoke.call_count, len(cases) + 2) - for actual, expected in zip(collected["cases"], cases): - self.assertEqual(actual["stdout"], expected["expected_stdout"]) - self.assertEqual(actual["status"], "PASS") - self.assertEqual(actual["binary"], "/tmp/ci-target/debug/cli") - - def test_output_mismatch_fails_and_build_failure_never_claims_examples_ran(self): - cases = examples.load_cases() - responses = [result(), result(json.dumps({"target_directory": "/tmp/target"}))] - responses += [result("wrong\n")] + [result(case["expected_stdout"]) for case in cases[1:]] - collected = report() - with patch.object(examples, "invoke", side_effect=responses): - examples.collect(collected) - self.assertEqual(collected["status"], "FAIL") - self.assertEqual(collected["cases"][0]["stdout"], "wrong\n") - self.assertEqual(collected["cases"][0]["status"], "FAIL") - self.assertIn("wrong", examples.markdown(collected)) - collected = report() - with patch.object(examples, "invoke", return_value=result(code=1)) as invoke: - examples.collect(collected) - self.assertEqual(invoke.call_count, 1) - self.assertEqual(collected["status"], "NOT_RUN") - self.assertTrue(all(case["status"] == "NOT_RUN" for case in collected["cases"])) - - def test_timeout_keeps_partial_utf8_stdout_and_stderr(self): - failure = subprocess.TimeoutExpired(["cli"], 1, output="日".encode(), stderr=b"partial error") - with patch.object(examples.subprocess, "run", side_effect=failure): - captured = examples.invoke(["cli"], 1) - self.assertIsNone(captured["exit_code"]) - self.assertEqual(captured["stdout"], "日") - self.assertEqual(captured["stderr"], "partial error") - self.assertIn("timed out", captured["error"]) - - def test_unexpected_failure_still_writes_both_artifacts(self): - with tempfile.TemporaryDirectory() as directory: - with patch("sys.argv", ["endor_examples.py", "--output-dir", directory]), \ - patch.object(examples, "collect", side_effect=ValueError("broken manifest")), \ - contextlib.redirect_stdout(io.StringIO()): - self.assertEqual(examples.main(), 1) - payload = json.loads(Path(directory, "endor-examples.json").read_text(encoding="utf-8")) - self.assertEqual(payload["status"], "FAIL") - self.assertEqual(payload["error"], "broken manifest") - self.assertIn("broken manifest", Path(directory, "endor-examples.md").read_text(encoding="utf-8")) - self.assertTrue(examples.fence("```\n日").startswith("````text\n")) - - -if __name__ == "__main__": - unittest.main()