From b2300af837365d75d6bd30dbb2ae7a24e2435679 Mon Sep 17 00:00:00 2001 From: "mm.ai" Date: Tue, 20 Oct 2026 13:01:18 +0900 Subject: [PATCH 01/24] fix(v2): separate frozen baseline from immutable current-bundle CI and legacy releases --- .github/workflows/dev.yml | 12 ++- .github/workflows/prod.yml | 8 ++ .github/workflows/v2-clean.yml | 7 +- .github/workflows/v2-dev-bundle.yml | 119 ++++++++++++++++++++++++++++ build/v2/README.md | 3 + release.yaml | 1 + tools/prepare-v2-source.py | 3 +- tools/release-dev162.sh | 6 ++ 8 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/v2-dev-bundle.yml diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index d558d2cb..d1797a2c 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -1,4 +1,4 @@ -name: CloudFile development checks +name: CloudFile shared/legacy regression checks on: push: @@ -49,5 +49,15 @@ jobs: - name: Install isolated check dependencies run: pip install --quiet -r cloudfile-docker/tools/check-requirements.txt + - name: Record checked shared/legacy source combination + run: | + { + echo '### Shared regression (not a V2 release certification)' + echo '- Docker: `'`git -C cloudfile-docker rev-parse HEAD`'`' + echo '- Server: `'`git -C cloudfile-server rev-parse HEAD`'`' + echo '- Hub: `'`git -C cloudfile-hub rev-parse HEAD`'`' + echo 'For the current V2 native source-set, use the separate V2 current bundle check.' + } >> "$GITHUB_STEP_SUMMARY" + - name: Run development checks run: ./cloudfile-docker/tools/run-checks.sh diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml index 66d73425..5a6fd785 100644 --- a/.github/workflows/prod.yml +++ b/.github/workflows/prod.yml @@ -112,6 +112,14 @@ jobs: path: cloudfile-docker fetch-depth: 1 + - name: Reject legacy production packaging on V2 dev + shell: bash + run: | + if [[ "$GITHUB_REF_NAME" == "dev" ]]; then + echo "::error::CloudFile dev is V2. This workflow packages the historical CE14 extension recipe; run a reviewed maintenance/archive ref for legacy versions, or use a separate approved V2 release path." + exit 2 + fi + - name: Read release refs id: refs working-directory: cloudfile-docker diff --git a/.github/workflows/v2-clean.yml b/.github/workflows/v2-clean.yml index ea680497..e570c449 100644 --- a/.github/workflows/v2-clean.yml +++ b/.github/workflows/v2-clean.yml @@ -1,10 +1,7 @@ -name: CloudFile V2 clean CE14 isolated checks +name: CloudFile frozen V2 source-set (historical manual check) +# Explicitly frozen 2026-10-16 source snapshot; this does NOT prove current dev. on: - push: - branches: [dev, dev2-clean, feature/v2-rust-guard-only, feature/v2-checkout-default-on] - pull_request: - branches: [dev] workflow_dispatch: permissions: diff --git a/.github/workflows/v2-dev-bundle.yml b/.github/workflows/v2-dev-bundle.yml new file mode 100644 index 00000000..d988d382 --- /dev/null +++ b/.github/workflows/v2-dev-bundle.yml @@ -0,0 +1,119 @@ +name: CloudFile V2 current dev source bundle + +on: + push: + branches: [dev] + pull_request: + branches: [dev] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: v2-current-source-${{ github.ref }} + cancel-in-progress: true + +jobs: + current-v2-source: + runs-on: ubuntu-24.04 + services: + mariadb: + image: mariadb:11.4 + env: + MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: '1' + ports: ['3306:3306'] + options: >- + --health-cmd="healthcheck.sh --connect --innodb_initialized" + --health-interval=5s --health-timeout=5s --health-retries=20 + redis: + image: redis:7-alpine + ports: ['6379:6379'] + options: >- + --health-cmd="redis-cli ping" + --health-interval=5s --health-timeout=5s --health-retries=10 + steps: + - uses: actions/checkout@v4 + with: + path: cloudfile-docker + fetch-depth: 0 + + - name: Freeze current peer commits once + shell: bash + id: peers + run: | + set -euo pipefail + server=$(git ls-remote https://github.com/eaopen/cloudfile-server.git refs/heads/dev | awk '{print $1}') + hub=$(git ls-remote https://github.com/eaopen/cloudfile-hub.git refs/heads/dev | awk '{print $1}') + [[ $server =~ ^[a-f0-9]{40}$ && $hub =~ ^[a-f0-9]{40}$ ]] + echo "server_sha=$server" >> "$GITHUB_OUTPUT" + echo "hub_sha=$hub" >> "$GITHUB_OUTPUT" + + - uses: actions/checkout@v4 + with: + repository: eaopen/cloudfile-server + ref: ${{ steps.peers.outputs.server_sha }} + path: cloudfile-server + fetch-depth: 0 + + - uses: actions/checkout@v4 + with: + repository: eaopen/cloudfile-hub + ref: ${{ steps.peers.outputs.hub_sha }} + path: cloudfile-hub + fetch-depth: 0 + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install isolated V2 contract dependencies + run: pip install --quiet 'pymysql>=1.1,<2' 'redis>=5,<8' 'PyJWT>=2.8,<3' 'requests>=2.31,<3' 'Django>=5.2,<5.3' + + - name: Prepare and test exact current V2 source snapshot + env: + SERVER_SHA: ${{ steps.peers.outputs.server_sha }} + HUB_SHA: ${{ steps.peers.outputs.hub_sha }} + CF_TEST_DB_PORT: '3306' + CF_TEST_REDIS_PORT: '6379' + shell: bash + run: | + set -euo pipefail + python3 - <<'PY' + import json + import os + from pathlib import Path + + manifest = json.loads(Path('cloudfile-docker/build/v2/source-set.json').read_text()) + manifest['components']['cloudfile-server']['overlay_commit'] = os.environ['SERVER_SHA'] + manifest['components']['cloudfile-hub']['overlay_commit'] = os.environ['HUB_SHA'] + Path('/tmp/current-v2-source-set.json').write_text(json.dumps(manifest, indent=2) + '\n') + PY + python3 cloudfile-docker/tools/prepare-v2-source.py \ + --workspace . --source-set /tmp/current-v2-source-set.json \ + --output /tmp/cloudfile-v2-current + python3 cloudfile-docker/tools/check-v2-fresh.py --source /tmp/cloudfile-v2-current + python3 -m compileall -q /tmp/cloudfile-v2-current/seahub/cloudfile_ext/v2 + + - name: Record independently checkable V2 bundle + if: always() + shell: bash + env: + SERVER_SHA: ${{ steps.peers.outputs.server_sha }} + HUB_SHA: ${{ steps.peers.outputs.hub_sha }} + run: | + { + echo '### V2 current DEV source snapshot' + echo "- Docker: `$(git -C cloudfile-docker rev-parse HEAD)`" + echo "- Server: `$SERVER_SHA`" + echo "- Hub: `$HUB_SHA`" + echo 'V2 source assembly, schema and isolated contracts only; not a live Etech or native CE runtime acceptance.' + } >> "$GITHUB_STEP_SUMMARY" + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: v2-source-receipt-${{ github.run_id }} + path: /tmp/cloudfile-v2-current/v2-source-receipt.json + if-no-files-found: ignore + retention-days: 14 diff --git a/build/v2/README.md b/build/v2/README.md index ab5fa472..8e135e3b 100644 --- a/build/v2/README.md +++ b/build/v2/README.md @@ -1,5 +1,8 @@ # Native V2 DEV Runtime +> As of 2026-10-20, `source-set.json` is a pinned historical baseline, not the current `dev` bundle. The `v2-dev-bundle.yml` workflow resolves exact Server/Hub HEAD SHAs at job start, generates a temporary source-set, assembles that exact combination and saves an audit receipt. The older `v2-clean.yml` runs only when explicitly requested. Neither check is a production image release. + + Prepare the pinned CE14 source with `tools/prepare-v2-source.py`, then use the existing CE14 dependency images and cache directory: diff --git a/release.yaml b/release.yaml index 58943d52..214dd0e2 100644 --- a/release.yaml +++ b/release.yaml @@ -1,3 +1,4 @@ +# Historical CE14 extension release recipe. V2 dev uses build/v2/source-set.json and the V2 current-bundle CI; do not release V2 images from this manifest. # CloudFile release manifest. # # Upstream CE 14 is released now: haiwen/seafile-server and haiwen/seahub both diff --git a/tools/prepare-v2-source.py b/tools/prepare-v2-source.py index ebdfabfd..9700d57d 100644 --- a/tools/prepare-v2-source.py +++ b/tools/prepare-v2-source.py @@ -116,8 +116,9 @@ def main(): parser.add_argument('--output', type=Path, required=True) parser.add_argument('--hub-ref') parser.add_argument('--server-ref') + parser.add_argument('--source-set', type=Path, help='Explicit immutable source-set manifest; defaults to frozen baseline.') args = parser.parse_args() - config = Path(__file__).resolve().parents[1] / 'build/v2/source-set.json' + config = args.source_set or (Path(__file__).resolve().parents[1] / 'build/v2/source-set.json') sources = json.loads(config.read_text()) refs = { 'cloudfile-hub': args.hub_ref or sources['components']['cloudfile-hub']['overlay_commit'], diff --git a/tools/release-dev162.sh b/tools/release-dev162.sh index fa8b74df..64f6b298 100755 --- a/tools/release-dev162.sh +++ b/tools/release-dev162.sh @@ -30,6 +30,12 @@ esac here=$(cd "$(dirname "$0")" && pwd) repo_root=$(cd "$here/.." && pwd) +# The old CE14 release.yaml and build path do not assemble V2 source-set. +# Reject a plausible but incorrect V2-dev image before touching any registry. +if [[ $(git -C "$repo_root" symbolic-ref --quiet --short HEAD 2>/dev/null || true) == dev ]]; then + echo "CloudFile dev is V2; this script packages legacy CE14 source. Use reviewed legacy maintenance/archive or a separately validated V2 release." >&2 + exit 2 +fi workspace=$(cd "$repo_root/.." && pwd) server_repo=$workspace/cloudfile-server hub_repo=$workspace/cloudfile-hub From 3565b2deeea77d2434a6f2adbb5d1589879b6de9 Mon Sep 17 00:00:00 2001 From: "mm.ai" Date: Tue, 20 Oct 2026 13:02:43 +0900 Subject: [PATCH 02/24] fix(ci): print exact shared source SHAs without shell quoting ambiguity --- .github/workflows/dev.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index d1797a2c..62a1f23e 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -53,9 +53,9 @@ jobs: run: | { echo '### Shared regression (not a V2 release certification)' - echo '- Docker: `'`git -C cloudfile-docker rev-parse HEAD`'`' - echo '- Server: `'`git -C cloudfile-server rev-parse HEAD`'`' - echo '- Hub: `'`git -C cloudfile-hub rev-parse HEAD`'`' + printf -- '- Docker: `%s`\n' "$(git -C cloudfile-docker rev-parse HEAD)" + printf -- '- Server: `%s`\n' "$(git -C cloudfile-server rev-parse HEAD)" + printf -- '- Hub: `%s`\n' "$(git -C cloudfile-hub rev-parse HEAD)" echo 'For the current V2 native source-set, use the separate V2 current bundle check.' } >> "$GITHUB_STEP_SUMMARY" From 46b00110a1bd938b1b9de54bdb40f5683d2303ba Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 13:49:02 +0900 Subject: [PATCH 03/24] ci(v2): require explicit dispatch for current cross-repo bundle verification --- .github/workflows/v2-dev-bundle.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/v2-dev-bundle.yml b/.github/workflows/v2-dev-bundle.yml index d988d382..107d18e3 100644 --- a/.github/workflows/v2-dev-bundle.yml +++ b/.github/workflows/v2-dev-bundle.yml @@ -1,10 +1,9 @@ name: CloudFile V2 current dev source bundle +# Run this integration snapshot only after the selected changes are ready. +# Ordinary commits and documentation edits must not trigger compilation or +# runtime test infrastructure; native builds remain separately approved. on: - push: - branches: [dev] - pull_request: - branches: [dev] workflow_dispatch: permissions: From a3dd747928fe34c69bb842864898d5d2e23e9f5e Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 13:49:53 +0900 Subject: [PATCH 04/24] fix(build): default source assembly to current dev and freeze refs once --- tools/prepare-v2-source.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/tools/prepare-v2-source.py b/tools/prepare-v2-source.py index 9700d57d..ea9352b3 100644 --- a/tools/prepare-v2-source.py +++ b/tools/prepare-v2-source.py @@ -116,14 +116,33 @@ def main(): parser.add_argument('--output', type=Path, required=True) parser.add_argument('--hub-ref') parser.add_argument('--server-ref') - parser.add_argument('--source-set', type=Path, help='Explicit immutable source-set manifest; defaults to frozen baseline.') + parser.add_argument('--source-set', type=Path, + help='Explicit frozen source-set; without this, select the local dev refs') args = parser.parse_args() config = args.source_set or (Path(__file__).resolve().parents[1] / 'build/v2/source-set.json') sources = json.loads(config.read_text()) - refs = { - 'cloudfile-hub': args.hub_ref or sources['components']['cloudfile-hub']['overlay_commit'], - 'cloudfile-server': args.server_ref or sources['components']['cloudfile-server']['overlay_commit'], + selected = { + 'cloudfile-hub': args.hub_ref, + 'cloudfile-server': args.server_ref, } + refs = {} + for component, requested in selected.items(): + # The CE source commits and patch allowlists stay pinned. Only CloudFile + # overlays default to the checked-out dev branch; freeze that ref once + # before any file is extracted. Explicit --source-set remains the route + # for historical/release snapshots. + ref = requested or (sources['components'][component]['overlay_commit'] + if args.source_set else 'dev') + try: + commit = git(args.workspace / component, 'rev-parse', '--verify', + ref + '^{commit}').decode().strip() + except subprocess.CalledProcessError as error: + raise ValueError('missing source ref for %s: %s' % (component, ref)) from error + if not re.fullmatch('[0-9a-f]{40}', commit): + raise ValueError('invalid resolved source commit for ' + component) + if not args.source_set: + sources['components'][component]['overlay_commit'] = commit + refs[component] = commit receipt = assemble(args.workspace.resolve(), args.output.resolve(), sources, refs) print(json.dumps(receipt, indent=2, sort_keys=True)) From 9dace83269f700cc879977ad1891ca799ae504ef Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 13:50:08 +0900 Subject: [PATCH 05/24] ci(v2): require explicit frozen source set for historical manual checks --- .github/workflows/v2-clean.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/v2-clean.yml b/.github/workflows/v2-clean.yml index e570c449..9e9d19eb 100644 --- a/.github/workflows/v2-clean.yml +++ b/.github/workflows/v2-clean.yml @@ -81,7 +81,7 @@ jobs: CF_TEST_REDIS_PORT: '6379' run: | python3 -m unittest discover -s cloudfile-docker/tests -p test_v2_source_assembly.py -v - python3 cloudfile-docker/tools/prepare-v2-source.py --workspace . --output /tmp/cfv2-clean + python3 cloudfile-docker/tools/prepare-v2-source.py --workspace . --source-set cloudfile-docker/build/v2/source-set.json --output /tmp/cfv2-clean python3 cloudfile-docker/tools/check-v2-fresh.py --source /tmp/cfv2-clean native-ce14-runtime: @@ -144,7 +144,7 @@ jobs: python3 -m pip install --quiet 'pymysql>=1.1,<2' 'redis>=5,<8' 'PyJWT>=2.8,<3' 'requests>=2.31,<3' 'Django>=5.2,<5.3' 'requests>=2.31,<3' 'PyJWT>=2,<3' - name: Assemble exact selected native source run: | - python3 cloudfile-docker/tools/prepare-v2-source.py --workspace . --output /tmp/cfv2-clean + python3 cloudfile-docker/tools/prepare-v2-source.py --workspace . --source-set cloudfile-docker/build/v2/source-set.json --output /tmp/cfv2-clean test ! -e /tmp/cfv2-clean/seahub/cloudfile_extensions test ! -e /tmp/cfv2-clean/seafile-server/server/cloudfile-policy.c - name: Check routed and native Go storage From ac2325e6b52220a5bcee785aa26751c7af649ead Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 13:50:41 +0900 Subject: [PATCH 06/24] docs(build): make current dev the explicit default and historical sources opt-in --- build/v2/README.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/build/v2/README.md b/build/v2/README.md index 8e135e3b..8590e329 100644 --- a/build/v2/README.md +++ b/build/v2/README.md @@ -1,10 +1,18 @@ # Native V2 DEV Runtime -> As of 2026-10-20, `source-set.json` is a pinned historical baseline, not the current `dev` bundle. The `v2-dev-bundle.yml` workflow resolves exact Server/Hub HEAD SHAs at job start, generates a temporary source-set, assembles that exact combination and saves an audit receipt. The older `v2-clean.yml` runs only when explicitly requested. Neither check is a production image release. - - -Prepare the pinned CE14 source with `tools/prepare-v2-source.py`, then use the -existing CE14 dependency images and cache directory: +> Server/Hub/Docker `dev` is the only default development source. Source assembly +> resolves the local Server/Hub `dev` refs to exact SHAs before reading files; +> update the local `dev` refs first. The pinned `source-set.json` is retained +> only as an explicitly selected historical snapshot with `--source-set`. +> Cross-repository integration checks run on manual `workflow_dispatch`, not +> on every code or documentation change. Neither source check publishes an image. + +Prepare the CE14 source plus reviewed current CloudFile `dev` overlays with +`python3 tools/prepare-v2-source.py --workspace . --output /path/to/prepared-source` +from a three-repository workspace. To reproduce a historical source snapshot, +supply `--source-set build/v2/source-set.json` explicitly instead. + +Then use the existing dependency images and cache directory: ```sh CF_BASE_IMAGE=cloudfile-build-base:ce14-v2 \ From 52849043aea97186294c035e566482022ad2d915 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 13:51:44 +0900 Subject: [PATCH 07/24] fix(build): isolate native compilation cache from Hub-only revision changes --- build/v2/Dockerfile.dev | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build/v2/Dockerfile.dev b/build/v2/Dockerfile.dev index a2e764c2..d0406a97 100644 --- a/build/v2/Dockerfile.dev +++ b/build/v2/Dockerfile.dev @@ -23,7 +23,9 @@ RUN --mount=type=cache,id=cfv2-gomod,from=cf-cache,source=gomodcache,target=/go/ go mod download FROM go-dependencies AS native -COPY v2-source-receipt.json /source/v2-source-receipt.json +# The combined receipt also changes on Hub-only commits; do not invalidate the +# expensive native C/Go layer with unrelated Hub source. The exact receipt is +# checked before the build and copied into the final runtime image below. COPY seafile-server /source/seafile-server RUN --mount=type=cache,id=cfv2-gomod,from=cf-cache,source=gomodcache,target=/go/pkg/mod \ --mount=type=cache,id=cfv2-gobuild-${TARGETARCH},from=cf-cache,source=gocache,target=/root/.cache/go-build \ From d6416545f018b139d94d41fec9de7036e6428e5f Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 13:51:47 +0900 Subject: [PATCH 08/24] fix(build): isolate native compilation cache from Hub-only revision changes --- build/v2/build-native.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/build/v2/build-native.sh b/build/v2/build-native.sh index a535ce8d..846f2dbe 100644 --- a/build/v2/build-native.sh +++ b/build/v2/build-native.sh @@ -8,7 +8,11 @@ case "$target" in all|sources|dependencies|server) ;; *) exit 2 ;; esac jobs=${CF_V2_BUILD_JOBS:-4} [[ $jobs =~ ^[1-9][0-9]*$ ]] || exit 2 if [[ $target != dependencies ]]; then - test -f "$source_root/v2-source-receipt.json" + # The host validates the full source receipt before Docker packaging. + # This server-only stage intentionally does not COPY the combined receipt: + # a Hub-only SHA update must not recompile native C/Go. + test -f "$source_root/seafile-server/scripts/sql/mysql/cloudfile-v2.sql" + test -f "$source_root/seafile-server/common/cf-v2-storage.c" test ! -d "$source_root/seahub/cloudfile_extensions" test ! -f "$source_root/seafile-server/common/cf-ext.c" fi From 64af7e5a28bfcb5c36039774fe488dc9e2f8e210 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 13:52:22 +0900 Subject: [PATCH 09/24] fix(build): validate exact assembled source receipt before incremental image operations --- build/v2/build-image.sh | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/build/v2/build-image.sh b/build/v2/build-image.sh index 5df93296..14396e79 100755 --- a/build/v2/build-image.sh +++ b/build/v2/build-image.sh @@ -4,6 +4,38 @@ set -euo pipefail here=$(cd "$(dirname "$0")" && pwd) repo=$(cd "$here/../.." && pwd) source_root=$(cd "${1:?prepared V2 source directory required}" && pwd) +# Read-only receipt check BEFORE any tagging, fetching or building. The +# combined receipt stays with the final image, not the Server C/Go build layer. +python3 - "$source_root" <<'PY' +import hashlib +import json +import re +import sys +from pathlib import Path + +root = Path(sys.argv[1]) +path = root / 'v2-source-receipt.json' +if not path.is_file() or path.is_symlink(): + raise SystemExit('Missing immutable V2 source receipt') +receipt = json.loads(path.read_text(encoding='utf-8')) +for component, directory in (('cloudfile-server', 'seafile-server'), + ('cloudfile-hub', 'seahub')): + item = receipt.get('components', {}).get(component, {}) + commits = (item.get('native_commit'), item.get('overlay_commit')) + if not all(isinstance(x, str) and re.fullmatch('[0-9a-f]{40}', x) for x in commits): + raise SystemExit('Incomplete source commits: ' + component) + checksums = item.get('overlay_sha256') + if not isinstance(checksums, dict) or not checksums: + raise SystemExit('Missing source checksums: ' + component) + for name, expected in checksums.items(): + relative = Path(name) + source = root / directory / relative + if (relative.is_absolute() or '..' in relative.parts or source.is_symlink() + or not source.is_file() or not isinstance(expected, str) + or hashlib.sha256(source.read_bytes()).hexdigest() != expected): + raise SystemExit('Source receipt mismatch: ' + component + '/' + name) +print('Prepared source receipt verified; no compilation performed') +PY image=${2:-cloudfile:v2-dev} platform=${CF_PLATFORM:-$(docker version --format '{{.Server.Os}}/{{.Server.Arch}}')} arch=${platform##*/} From 72a1e6b82c6d887a1aef7143772ccf87e74e991b Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 13:59:17 +0900 Subject: [PATCH 10/24] fix(build): include current Go filesystem storage seam in exact dev source bundle --- .github/workflows/v2-dev-bundle.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/v2-dev-bundle.yml b/.github/workflows/v2-dev-bundle.yml index 107d18e3..6cc05411 100644 --- a/.github/workflows/v2-dev-bundle.yml +++ b/.github/workflows/v2-dev-bundle.yml @@ -86,6 +86,12 @@ jobs: manifest = json.loads(Path('cloudfile-docker/build/v2/source-set.json').read_text()) manifest['components']['cloudfile-server']['overlay_commit'] = os.environ['SERVER_SHA'] manifest['components']['cloudfile-hub']['overlay_commit'] = os.environ['HUB_SHA'] + # The Go filesystem backend is an execution seam, not an additive + # V2 module. Without it a current-dev image can silently retain the + # upstream tempfiles EXDEV behavior even after Server's fix merges. + native = manifest['components']['cloudfile-server']['native_patches'] + if 'fileserver/objstore/backend_fs.go' not in native: + native.append('fileserver/objstore/backend_fs.go') Path('/tmp/current-v2-source-set.json').write_text(json.dumps(manifest, indent=2) + '\n') PY python3 cloudfile-docker/tools/prepare-v2-source.py \ From 7bdbc80ce9519d3b2e9028818cbda917f99e4fb0 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 14:01:55 +0900 Subject: [PATCH 11/24] fix(release): forbid historical build fallback to floating dev refs --- .github/workflows/prod.yml | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml index 5a6fd785..d5230aad 100644 --- a/.github/workflows/prod.yml +++ b/.github/workflows/prod.yml @@ -112,11 +112,11 @@ jobs: path: cloudfile-docker fetch-depth: 1 - - name: Reject legacy production packaging on V2 dev + - name: Restrict the historical release recipe to explicit maintenance refs shell: bash run: | - if [[ "$GITHUB_REF_NAME" == "dev" ]]; then - echo "::error::CloudFile dev is V2. This workflow packages the historical CE14 extension recipe; run a reviewed maintenance/archive ref for legacy versions, or use a separate approved V2 release path." + if [[ "$GITHUB_REF_NAME" != maintenance/v0.9/* ]]; then + echo "::error::Historical packaging is allowed only from an explicitly approved maintenance/v0.9/* ref; never from CloudFile dev or a detached feature snapshot." exit 2 fi @@ -124,14 +124,21 @@ jobs: id: refs working-directory: cloudfile-docker run: | - server_ref=$(python3 build/cloudfile_14.0/read-manifest.py release.yaml server_commit || true) - hub_ref=$(python3 build/cloudfile_14.0/read-manifest.py release.yaml hub_commit || true) - if [[ -z $server_ref ]]; then - server_ref=$(python3 build/cloudfile_14.0/read-manifest.py release.yaml forks.cloudfile_server.ref) - fi - if [[ -z $hub_ref ]]; then - hub_ref=$(python3 build/cloudfile_14.0/read-manifest.py release.yaml forks.cloudfile_hub.ref) - fi + server_ref=$(python3 build/cloudfile_14.0/read-manifest.py release.yaml server_commit) + hub_ref=$(python3 build/cloudfile_14.0/read-manifest.py release.yaml hub_commit) + docker_ref=$(python3 build/cloudfile_14.0/read-manifest.py release.yaml docker_commit) + # A legacy maintenance build must never fall back to the current + # floating dev branches after changing only the Docker ref. + for pin in "$server_ref" "$hub_ref" "$docker_ref"; do + if [[ ! $pin =~ ^[0-9a-f]{40}$ ]]; then + echo "::error::Historical release requires all three immutable *_commit pins" + exit 2 + fi + done + [[ $docker_ref == "$(git rev-parse HEAD)" ]] || { + echo "::error::Historical Docker commit differs from frozen release manifest" + exit 2 + } echo "server_ref=$server_ref" >> "$GITHUB_OUTPUT" echo "hub_ref=$hub_ref" >> "$GITHUB_OUTPUT" From 39571f762f93971e8089cd8d6857f243f9d5b540 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 14:02:50 +0900 Subject: [PATCH 12/24] fix(release): require explicit maintenance SHA tuple before any legacy package or push --- tools/release-dev162.sh | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/tools/release-dev162.sh b/tools/release-dev162.sh index 64f6b298..db43994e 100755 --- a/tools/release-dev162.sh +++ b/tools/release-dev162.sh @@ -30,10 +30,12 @@ esac here=$(cd "$(dirname "$0")" && pwd) repo_root=$(cd "$here/.." && pwd) -# The old CE14 release.yaml and build path do not assemble V2 source-set. -# Reject a plausible but incorrect V2-dev image before touching any registry. -if [[ $(git -C "$repo_root" symbolic-ref --quiet --short HEAD 2>/dev/null || true) == dev ]]; then - echo "CloudFile dev is V2; this script packages legacy CE14 source. Use reviewed legacy maintenance/archive or a separately validated V2 release." >&2 +# This command intentionally packages the historical recipe only. Require +# an explicit maintenance branch even for push-only mode or detached HEAD; +# never let an old recipe package current dev just because the checkout moved. +docker_branch=$(git -C "$repo_root" symbolic-ref --quiet --short HEAD 2>/dev/null || true) +if [[ "$docker_branch" != maintenance/v0.9/* ]]; then + echo "Historical release requires an approved maintenance/v0.9/* Docker branch; current dev and detached refs are refused." >&2 exit 2 fi workspace=$(cd "$repo_root/.." && pwd) @@ -41,6 +43,19 @@ server_repo=$workspace/cloudfile-server hub_repo=$workspace/cloudfile-hub manifest=$repo_root/release.yaml reader=$repo_root/build/cloudfile_14.0/read-manifest.py +server_pin=$(python3 "$reader" "$manifest" server_commit) +hub_pin=$(python3 "$reader" "$manifest" hub_commit) +docker_pin=$(python3 "$reader" "$manifest" docker_commit) +for pin in "$server_pin" "$hub_pin" "$docker_pin"; do + [[ "$pin" =~ ^[0-9a-f]{40}$ ]] || { + echo "Historical release requires explicit immutable server/hub/docker commits." >&2 + exit 2 + } +done +[[ "$docker_pin" == "$(git -C "$repo_root" rev-parse HEAD)" ]] || { + echo "Historical Docker checkout differs from frozen commit." >&2 + exit 2 +} dist=$repo_root/build/cloudfile_14.0/seafile-server-$version local_image=$(python3 "$reader" "$manifest" image) local_image=${local_image%%:*}:$version @@ -110,11 +125,10 @@ if [[ $mode != push ]]; then echo "Updating $(basename "$repo") ($branch)" git_at "$repo" pull --ff-only origin "$branch" done - server_ref=$(python3 "$reader" "$manifest" forks.cloudfile_server.ref) - hub_ref=$(python3 "$reader" "$manifest" forks.cloudfile_hub.ref) - [[ $(git_at "$server_repo" symbolic-ref --short HEAD) == "$server_ref" && - $(git_at "$hub_repo" symbolic-ref --short HEAD) == "$hub_ref" ]] || { - echo "Local Server/Hub branches differ from release.yaml refs." >&2 + [[ $(git_at "$server_repo" rev-parse HEAD) == "$server_pin" && + $(git_at "$hub_repo" rev-parse HEAD) == "$hub_pin" && + $(git_at "$repo_root" rev-parse HEAD) == "$docker_pin" ]] || { + echo "Historical checkout differs from frozen Server/Hub/Docker commit; do not build floating dev." >&2 exit 2 } if [[ ${CF_FORCE_REBUILD:-0} != 1 ]] && can_reuse_build; then From c3ea11b77a43409fdcacf444e29cc3ad8d482e91 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 14:05:58 +0900 Subject: [PATCH 13/24] fix(release): record Docker checkout SHA without impossible self-referential pin --- .github/workflows/prod.yml | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml index d5230aad..075b9c58 100644 --- a/.github/workflows/prod.yml +++ b/.github/workflows/prod.yml @@ -126,19 +126,14 @@ jobs: run: | server_ref=$(python3 build/cloudfile_14.0/read-manifest.py release.yaml server_commit) hub_ref=$(python3 build/cloudfile_14.0/read-manifest.py release.yaml hub_commit) - docker_ref=$(python3 build/cloudfile_14.0/read-manifest.py release.yaml docker_commit) - # A legacy maintenance build must never fall back to the current - # floating dev branches after changing only the Docker ref. - for pin in "$server_ref" "$hub_ref" "$docker_ref"; do + # Never fall back to floating dev; the Docker commit is recorded + # from the checked-out workflow SHA, not self-pinned in this file. + for pin in "$server_ref" "$hub_ref"; do if [[ ! $pin =~ ^[0-9a-f]{40}$ ]]; then echo "::error::Historical release requires all three immutable *_commit pins" exit 2 fi done - [[ $docker_ref == "$(git rev-parse HEAD)" ]] || { - echo "::error::Historical Docker commit differs from frozen release manifest" - exit 2 - } echo "server_ref=$server_ref" >> "$GITHUB_OUTPUT" echo "hub_ref=$hub_ref" >> "$GITHUB_OUTPUT" From ee8ad013c8b56ac04de136f6b97c197278c7052f Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 14:06:01 +0900 Subject: [PATCH 14/24] fix(release): record Docker checkout SHA without impossible self-referential pin --- tools/release-dev162.sh | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tools/release-dev162.sh b/tools/release-dev162.sh index db43994e..9aec936a 100755 --- a/tools/release-dev162.sh +++ b/tools/release-dev162.sh @@ -45,17 +45,14 @@ manifest=$repo_root/release.yaml reader=$repo_root/build/cloudfile_14.0/read-manifest.py server_pin=$(python3 "$reader" "$manifest" server_commit) hub_pin=$(python3 "$reader" "$manifest" hub_commit) -docker_pin=$(python3 "$reader" "$manifest" docker_commit) -for pin in "$server_pin" "$hub_pin" "$docker_pin"; do +# The Docker SHA is captured from the checkout in the build receipt. +# A commit cannot embed its own SHA in a tracked manifest. +for pin in "$server_pin" "$hub_pin"; do [[ "$pin" =~ ^[0-9a-f]{40}$ ]] || { echo "Historical release requires explicit immutable server/hub/docker commits." >&2 exit 2 } done -[[ "$docker_pin" == "$(git -C "$repo_root" rev-parse HEAD)" ]] || { - echo "Historical Docker checkout differs from frozen commit." >&2 - exit 2 -} dist=$repo_root/build/cloudfile_14.0/seafile-server-$version local_image=$(python3 "$reader" "$manifest" image) local_image=${local_image%%:*}:$version @@ -126,9 +123,8 @@ if [[ $mode != push ]]; then git_at "$repo" pull --ff-only origin "$branch" done [[ $(git_at "$server_repo" rev-parse HEAD) == "$server_pin" && - $(git_at "$hub_repo" rev-parse HEAD) == "$hub_pin" && - $(git_at "$repo_root" rev-parse HEAD) == "$docker_pin" ]] || { - echo "Historical checkout differs from frozen Server/Hub/Docker commit; do not build floating dev." >&2 + $(git_at "$hub_repo" rev-parse HEAD) == "$hub_pin" ]] || { + echo "Historical Server/Hub checkout differs from frozen commits; do not build floating dev." >&2 exit 2 } if [[ ${CF_FORCE_REBUILD:-0} != 1 ]] && can_reuse_build; then From 9d217c965be04d61fe01d3e41871355426656e81 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 14:08:13 +0900 Subject: [PATCH 15/24] docs(release): align frozen source pin messages with recorded Docker checkout SHA --- .github/workflows/prod.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/prod.yml b/.github/workflows/prod.yml index 075b9c58..e4367b04 100644 --- a/.github/workflows/prod.yml +++ b/.github/workflows/prod.yml @@ -130,7 +130,7 @@ jobs: # from the checked-out workflow SHA, not self-pinned in this file. for pin in "$server_ref" "$hub_ref"; do if [[ ! $pin =~ ^[0-9a-f]{40}$ ]]; then - echo "::error::Historical release requires all three immutable *_commit pins" + echo "::error::Historical release requires immutable server_commit and hub_commit pins" exit 2 fi done From 5267b3718360e6fff8ba6567268335848d32e1aa Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 14:08:17 +0900 Subject: [PATCH 16/24] docs(release): align frozen source pin messages with recorded Docker checkout SHA --- tools/release-dev162.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/release-dev162.sh b/tools/release-dev162.sh index 9aec936a..a970f546 100755 --- a/tools/release-dev162.sh +++ b/tools/release-dev162.sh @@ -49,7 +49,7 @@ hub_pin=$(python3 "$reader" "$manifest" hub_commit) # A commit cannot embed its own SHA in a tracked manifest. for pin in "$server_pin" "$hub_pin"; do [[ "$pin" =~ ^[0-9a-f]{40}$ ]] || { - echo "Historical release requires explicit immutable server/hub/docker commits." >&2 + echo "Historical release requires explicit immutable server/hub commits; Docker SHA is recorded from its checkout." >&2 exit 2 } done From c043594a7ff781e3e3bf635700252a082a5e1c51 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 14:21:49 +0900 Subject: [PATCH 17/24] ci: replace default full cross-repo checks with lightweight source checks [skip ci] --- .github/workflows/dev.yml | 98 +++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 50 deletions(-) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 62a1f23e..cbcf191d 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -1,63 +1,61 @@ -name: CloudFile shared/legacy regression checks +name: CloudFile quick source checks +# Routine dev and PR changes must never trigger a cross-repository build, +# native compilation, DB integration or deployment. Run those separately via +# reviewed workflow_dispatch jobs when the relevant change needs them. on: push: branches: [dev] + paths-ignore: + - '**/*.md' + - '**/*.markdown' + - 'README*' + - 'AGENTS.md' + - 'docs/**' + pull_request: + branches: [dev] + paths-ignore: + - '**/*.md' + - '**/*.markdown' + - 'README*' + - 'AGENTS.md' + - 'docs/**' + workflow_dispatch: -concurrency: - group: dev-checks-${{ github.ref }} - cancel-in-progress: true +permissions: + contents: read jobs: - checks: + static: runs-on: ubuntu-24.04 + timeout-minutes: 5 steps: - uses: actions/checkout@v4 with: - path: cloudfile-docker - fetch-depth: 0 - - - uses: actions/checkout@v4 - with: - repository: eaopen/cloudfile-server - ref: dev - path: cloudfile-server - fetch-depth: 0 - - - uses: actions/checkout@v4 - with: - repository: eaopen/cloudfile-hub - ref: dev - path: cloudfile-hub - fetch-depth: 0 - - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - uses: actions/setup-go@v5 - with: - go-version-file: cloudfile-server/fileserver/go.mod - cache-dependency-path: cloudfile-server/fileserver/go.sum - - - name: Install C test dependencies + fetch-depth: 2 + - name: Check changed files + shell: bash run: | - sudo apt-get update -qq - # Native directory paging tests compile the real Vala objects and JSON RPC. - sudo apt-get install -y -qq libglib2.0-dev libjansson-dev valac pkg-config - - - name: Install isolated check dependencies - run: pip install --quiet -r cloudfile-docker/tools/check-requirements.txt - - - name: Record checked shared/legacy source combination + set -euo pipefail + git diff --check HEAD^ HEAD + python3 - <<'PY' + from pathlib import Path + import subprocess + + changed = subprocess.check_output( + ['git', 'diff', '--name-only', '-z', 'HEAD^', 'HEAD'] + ).split(b'\0') + for raw in changed: + if not raw: + continue + path = Path(raw.decode('utf-8')) + if path.is_file() and path.suffix == '.py': + compile(path.read_bytes(), str(path), 'exec') + PY + while IFS= read -r path; do + [[ ! -f "$path" ]] || bash -n "$path" + done < <(git diff --name-only HEAD^ HEAD -- '*.sh') + + - name: Test source selection rules without building images run: | - { - echo '### Shared regression (not a V2 release certification)' - printf -- '- Docker: `%s`\n' "$(git -C cloudfile-docker rev-parse HEAD)" - printf -- '- Server: `%s`\n' "$(git -C cloudfile-server rev-parse HEAD)" - printf -- '- Hub: `%s`\n' "$(git -C cloudfile-hub rev-parse HEAD)" - echo 'For the current V2 native source-set, use the separate V2 current bundle check.' - } >> "$GITHUB_STEP_SUMMARY" - - - name: Run development checks - run: ./cloudfile-docker/tools/run-checks.sh + python3 -m unittest discover -s tests -p test_v2_source_assembly.py -q From 0c65a5d4377145464302d4f56efa501eccc7e823 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 14:22:02 +0900 Subject: [PATCH 18/24] ci: run native compile only by explicit approval [skip ci] --- .github/workflows/cf-edit-native.yml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/.github/workflows/cf-edit-native.yml b/.github/workflows/cf-edit-native.yml index 1b752c11..cedc92b8 100644 --- a/.github/workflows/cf-edit-native.yml +++ b/.github/workflows/cf-edit-native.yml @@ -1,16 +1,7 @@ name: CF-EDIT pinned native compile (feature) on: - push: - branches: [feature/v2-rust-guard-only, dev] - paths: - - "build/v2/source-set.json" - - ".github/workflows/cf-edit-native.yml" - pull_request: - branches: [dev] - paths: - - "build/v2/source-set.json" - - ".github/workflows/cf-edit-native.yml" workflow_dispatch: + permissions: contents: read jobs: From bd4fc52757d825536a2873d598551751659a78a7 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 14:24:08 +0900 Subject: [PATCH 19/24] test(ci): assert low-cost build and source selection invariants [skip ci] --- tests/test_v2_build_policy.py | 59 +++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 tests/test_v2_build_policy.py diff --git a/tests/test_v2_build_policy.py b/tests/test_v2_build_policy.py new file mode 100644 index 00000000..cd22cc6a --- /dev/null +++ b/tests/test_v2_build_policy.py @@ -0,0 +1,59 @@ +"""Cheap source and CI policy regressions (no Docker, compiler or network).""" +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] + + +def read(path): + return (ROOT / path).read_text(encoding="utf-8") + + +class DevSourcePolicy(unittest.TestCase): + def test_current_dev_is_default_but_historical_snapshot_is_explicit(self): + assembler = read("tools/prepare-v2-source.py") + self.assertIn("if args.source_set else 'dev'", assembler) + self.assertIn("sources['components'][component]['overlay_commit'] = commit", assembler) + frozen = read(".github/workflows/v2-clean.yml") + self.assertEqual( + frozen.count("--source-set cloudfile-docker/build/v2/source-set.json"), 2 + ) + + def test_hub_only_revision_does_not_invalidate_native_compiler_layer(self): + dockerfile = read("build/v2/Dockerfile.dev") + server_layer = dockerfile.split("FROM go-dependencies AS native", 1)[1].split( + "FROM ${CF_BASE_IMAGE} AS frontend-dependencies", 1 + )[0] + self.assertNotIn("COPY v2-source-receipt.json", server_layer) + self.assertIn("COPY seafile-server /source/seafile-server", server_layer) + self.assertIn( + "COPY v2-source-receipt.json /opt/cloudfile-v2/source/v2-source-receipt.json", + dockerfile, + ) + preflight = read("build/v2/build-image.sh") + self.assertLess( + preflight.index("Prepared source receipt verified"), + preflight.index('docker tag "$build_id"'), + ) + + def test_current_bundle_includes_the_go_storage_seam(self): + current = read(".github/workflows/v2-dev-bundle.yml") + self.assertIn("native.append('fileserver/objstore/backend_fs.go')", current) + + def test_native_and_release_workflows_never_auto_run(self): + paths = ( + ".github/workflows/v2-clean.yml", + ".github/workflows/v2-dev-bundle.yml", + ".github/workflows/cf-edit-native.yml", + ".github/workflows/prod.yml", + ) + for path in paths: + with self.subTest(workflow=path): + trigger = read(path).split("permissions:", 1)[0] + self.assertIn("workflow_dispatch:", trigger) + self.assertNotIn("\n push:", trigger) + self.assertNotIn("\n pull_request:", trigger) + + +if __name__ == "__main__": + unittest.main() From bb15532b58ba9a1403d39c55e40afdd3c0300207 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 14:24:28 +0900 Subject: [PATCH 20/24] ci: include cheap build-policy tests in routine source checks [skip ci] --- .github/workflows/dev.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index cbcf191d..c6a3f02a 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -59,3 +59,4 @@ jobs: - name: Test source selection rules without building images run: | python3 -m unittest discover -s tests -p test_v2_source_assembly.py -q + python3 -m unittest discover -s tests -p test_v2_build_policy.py -q From c17ab9cefc45dfc2af1f8799d1a08c292a54dd33 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 14:25:03 +0900 Subject: [PATCH 21/24] docs(build): document current dev source, incremental cache, and manual CI policy [skip ci] --- build/v2/README.md | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/build/v2/README.md b/build/v2/README.md index 8590e329..a126bac6 100644 --- a/build/v2/README.md +++ b/build/v2/README.md @@ -7,6 +7,32 @@ > Cross-repository integration checks run on manual `workflow_dispatch`, not > on every code or documentation change. Neither source check publishes an image. +### Current CI and incremental-build policy (2026-10-20) + +- Ordinary Server/Hub/Docker pull requests and `dev` pushes perform cheap + changed-source syntax/format checks only; pure Markdown changes skip CI. + Search/OIDC probes retain narrow path filters. +- Full Seahub, C/Go/native, database, cross-repository and image builds are + **manual**. Reuse previous test evidence if its source SHA, prerequisites and + runner environment are unchanged. Neither CI nor release performs a deployment + or an automatic rollback. +- `prepare-v2-source.py` selects the local Server/Hub `dev` refs by default, + resolves the SHAs once, and records the exact reviewed overlay in its receipt. + The CE14 native anchor and allowlisted native file seams do not float. +- The combined source receipt is validated before any Docker build and retained + in the final image, **not** fed into the C/Go compiler layer. A Hub-only SHA + change should reuse that existing layer; this is a cache-key guarantee, + **not** a measured cache-hit claim. BuildKit, ccache, Go, npm and pip caches + may still be absent on a particular builder. +- Legacy `release.yaml`/CE14 packaging is restricted to deliberately selected + maintenance refs and fixed Server/Hub SHA inputs, never the default V2 path. + Missing runtime launchers or invalid input hashes must fail explicitly rather + than copying or silently falling back to historical application files. + +**Approval boundary:** a manual integration or release check is not authorization +to compile all targets, publish an image, change DEV/PROD containers, or roll +back a deployment. Each operation requires its own approved scope. + Prepare the CE14 source plus reviewed current CloudFile `dev` overlays with `python3 tools/prepare-v2-source.py --workspace . --output /path/to/prepared-source` from a three-repository workspace. To reproduce a historical source snapshot, @@ -217,7 +243,10 @@ reproduces RawPostDataException on the prior image; no schema, tag migration or native binary rebuild is involved. -## 内部文件读取:复用 Seafile 原生票据(2026-10-19) +## 内部文件读取:复用 Seafile 原生票据(2026-10-19,历史构建快照) + +> 以下源码 SHA 与当时的构建限制用于复现 10 月 19 日的特定候选; +> **不是当前 `dev` 的源码选择规则**。当前构建规则以上方说明为准。 本候选版本固定 Server `89ae96652389024730ac9d3927ed3be908534ce9` 与 Hub `bfc7651f188b8a537a466e7ee0bc143bc6acb85e`。 From 3a430db7066f44cb181c5893624b84613ec265db Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 14:29:57 +0900 Subject: [PATCH 22/24] fix(build): include current Go storage seam in default dev assembly [skip ci] --- tools/prepare-v2-source.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/prepare-v2-source.py b/tools/prepare-v2-source.py index ea9352b3..40ee5f25 100644 --- a/tools/prepare-v2-source.py +++ b/tools/prepare-v2-source.py @@ -121,6 +121,14 @@ def main(): args = parser.parse_args() config = args.source_set or (Path(__file__).resolve().parents[1] / 'build/v2/source-set.json') sources = json.loads(config.read_text()) + if not args.source_set: + # Preserve the historical allowlist only for explicit frozen builds. + # The current Server dev filesystem backend writes each block beside + # its final path; omitting this seam silently restores cross-mount EXDEV. + native_patches = sources['components']['cloudfile-server']['native_patches'] + go_seam = 'fileserver/objstore/backend_fs.go' + if go_seam not in native_patches: + native_patches.append(go_seam) selected = { 'cloudfile-hub': args.hub_ref, 'cloudfile-server': args.server_ref, From f261be4ed16aaba6545a3b11246f2b12d8ed45f2 Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 14:30:12 +0900 Subject: [PATCH 23/24] test(build): cover Go filesystem seam in default dev assembly [skip ci] --- tests/test_v2_build_policy.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_v2_build_policy.py b/tests/test_v2_build_policy.py index cd22cc6a..b1a66b74 100644 --- a/tests/test_v2_build_policy.py +++ b/tests/test_v2_build_policy.py @@ -14,6 +14,8 @@ def test_current_dev_is_default_but_historical_snapshot_is_explicit(self): assembler = read("tools/prepare-v2-source.py") self.assertIn("if args.source_set else 'dev'", assembler) self.assertIn("sources['components'][component]['overlay_commit'] = commit", assembler) + self.assertIn("go_seam = 'fileserver/objstore/backend_fs.go'", assembler) + self.assertIn("native_patches.append(go_seam)", assembler) frozen = read(".github/workflows/v2-clean.yml") self.assertEqual( frozen.count("--source-set cloudfile-docker/build/v2/source-set.json"), 2 From 06dc36f7db04b86e88a0acef3c0f6964669db08c Mon Sep 17 00:00:00 2001 From: servicememeai Date: Tue, 20 Oct 2026 14:30:33 +0900 Subject: [PATCH 24/24] docs(build): clarify immutable CE anchor versus current native seams [skip ci] --- tools/prepare-v2-source.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/prepare-v2-source.py b/tools/prepare-v2-source.py index 40ee5f25..93febba3 100644 --- a/tools/prepare-v2-source.py +++ b/tools/prepare-v2-source.py @@ -135,10 +135,10 @@ def main(): } refs = {} for component, requested in selected.items(): - # The CE source commits and patch allowlists stay pinned. Only CloudFile - # overlays default to the checked-out dev branch; freeze that ref once - # before any file is extracted. Explicit --source-set remains the route - # for historical/release snapshots. + # CE native commits remain immutable. Review the CloudFile overlay + # plus its minimal native seams from the local dev ref and freeze the + # chosen commit before extraction. Explicit --source-set reproduces + # the exact historical/release allowlist. ref = requested or (sources['components'][component]['overlay_commit'] if args.source_set else 'dev') try: