Relay deploys over NetBird and move staging to compute01 (#686) #688
Closed
kayg
wants to merge 1 commit from
issue/686-netbird-deploy into dev
pull from: issue/686-netbird-deploy
merge into: kayg:dev
kayg:main
kayg:feat/style-sheet-fixes
kayg:feat/contrast
kayg:fix/shared-row-swipes
kayg:feat/pass8
kayg:t3/paged-search-reconcile-final
kayg:storage/wal-checkpoint-off-save-1051
kayg:review/calendar-2
kayg:dev
kayg:feat/collections-db
kayg:fix/instant-keys-1056
kayg:feat/money-round
kayg:perf/stalls-1054
kayg:wip/tiering-v3-cold4
kayg:wip/tiering-v3-cold5
kayg:port/rows-actions
kayg:port/rows-drop
kayg:port/hidden-blocks
kayg:port/rows
kayg:port/port-tables
kayg:port/port-tables-2
kayg:t3/mail-1011-liveness
kayg:port/devices
kayg:port/port-attachments
kayg:port/port-attachments-2
kayg:port/port-attachments-3
kayg:docs/design-consistency
kayg:docs/engineering-standards
kayg:fix/rusqlite-0.41-rustsec-2026-0459
kayg:feat/iphone-fixes
kayg:shared/restore-and-stale-918-919
kayg:fix/phone-top-band-contrast
kayg:feat/notes-approve
kayg:perf/soak-max-gate
kayg:feat/files-s1-identity
kayg:fix/lifecycle-event-id
kayg:feat/quickfix
kayg:wip/tiering-v3-cold
kayg:perf/web-mockup-frame-fixes
kayg:port/contacts-merge
kayg:port/contacts-edit
kayg:test/shared-room-no-deleted-text
kayg:perf/first-paint-credits
kayg:fix/shared-history-after-grant
kayg:fix/phone-swipe-regressions
kayg:feat/contrast-r1
kayg:review/sharing-pr3-review
kayg:feat/tiering-v3-s2-tags
kayg:feat/folder-drop
kayg:fix/deskpills-lazy-share
kayg:feat/sharing-s1-pr3
kayg:tmp/money-round-before
kayg:fix/phone-pane-backdrop-roots
kayg:fix/palette-preload-chips
kayg:fix/invite-defaults
kayg:port/tasks-matrix
kayg:fix/restore-green-gates
kayg:fix/sharing-fast-path-hardening
kayg:t3/photos-s1
kayg:feat/tiering-v3-s1-namespace
kayg:notes-archive-hide
kayg:feat/tasks-actions
kayg:t3/photos-plan-slice-one
kayg:feat/whiteboard-s1
kayg:t3/paged-search-reconcile
kayg:t3/smooth-pane-transitions
kayg:t3/index-position-startup-scan
kayg:fix/private-staging-759b-r2
kayg:feat/dav-transport
kayg:feat/imap-notes-fetch
kayg:feat/imap-notes-projection
kayg:feat/imap-notes-readonly
kayg:feat/imap-notes-auth
kayg:feat/imap-notes-server-foundation
kayg:docs/undo-block-ids-plan
kayg:fix/undo-768
kayg:proto/block-identity-v7d
kayg:issue/carddav-read-perf-fix-r2
kayg:issue/carddav-read-r2
kayg:docs/imap-766-plan-v2
kayg:docs/imap-notes-plan
kayg:docs/quota-plan-2
kayg:fix/parallel-test-flakes
kayg:feat/contacts-companion-pure
kayg:docs/calendar-backend-v2-plan
kayg:feat/calendar-ical-converter
kayg:docs/tasks-writes-v2-plan
kayg:docs/carddav-writes-v2-plan
kayg:docs/contacts-import-v2-plan
kayg:issue/610-pm-midnight
kayg:feat/calendar-v2-s1-containment
kayg:docs/dav-recorder-v2-plan
kayg:plan/previews-865
kayg:plan/save-log-durable-entry
kayg:fix/e2e-data-on-ram
kayg:feat/appwindow3
kayg:feat/appwindow2
kayg:feat/appwindow
kayg:feat/money-p1
kayg:feat/keys-sheet
kayg:review/calendar-2-themes
kayg:feat/smooth-cursor
kayg:fix/float-style
kayg:review/calendar-2-float
kayg:release/2026-11-06-hotfix
kayg:archive/port/rows-drop-code
kayg:archive/port/rows-actions
kayg:archive/port/rows
kayg:scratch/tasks-894-ui-gates
kayg:review/calendar-2-night-wip
kayg:release/2026-11-05
kayg:archive/feat/tiering-v3-s1-namespace
kayg:archive/flaky/rows-drop-fixed
kayg:archive/flaky/rows-actions
kayg:archive/feat/tiering-v3-reader
kayg:archive/feat/tiering-v3-roots
kayg:archive/flaky/rows
kayg:sim/bm15-final
kayg:sim/bm15-fix
kayg:sim/bm15-bis-932
kayg:sim/bm15-bis-909
kayg:sim/bm15
kayg:sim/bm15-bis-887
kayg:perf/tiering-option-b
kayg:port/style-3
kayg:port/style-2
kayg:port/style
kayg:fix/sw-lazy-precache
kayg:fix/vim-w-waits-for-save
kayg:fix/signout-test-races
kayg:fp-scratch/all
kayg:sim/bm15-first-paint
kayg:archive/bm15/rows-drop
kayg:archive/bm15/rows-actions
kayg:fix/palette-snippet-contrast
kayg:archive/bm15/rows
kayg:archive/bm15/hidden
kayg:archive/plan/save-log-durable-entry
kayg:mock/money-fix2
kayg:archive/fix/e2e-data-on-ram
kayg:mock/appearance-all
kayg:mock/pill-press
kayg:archive/docs/design-consistency
kayg:mock/auto-swipes
kayg:mock/mail-actions
kayg:archive/feat/mail-p1
kayg:mock/money-static
kayg:archive/review/standards-917
kayg:archive/docs/engineering-standards
kayg:perf/tiering-761-fix
kayg:mock/type-audit
kayg:mock/third-pane
kayg:feat/contacts-import-v2-s1
kayg:perf/executor-hot-path
kayg:archive/port/hidden-blocks
kayg:archive/port/devices
kayg:scratch/executor-on-894
kayg:scratch/executor-spans2
kayg:scratch/executor-spans
kayg:train/2026-11-03-bm14c
kayg:archive/port/rows-drop
kayg:feat/tiering-761-d-scale
kayg:feat/tiering-761-c-index
kayg:feat/tiering-761-b-reader
kayg:archive/review/sharing-pr3-review-9fec3d1f
kayg:train/2026-11-03-bm14b
kayg:perf/ports-lazy-budgets
kayg:feat/tiering-761-a-migration
kayg:train/2026-11-03-bm14
kayg:train/2026-11-03-bm12
kayg:archive/review/sharing-pr3-review
kayg:archive/feat/sharing-s1-pr3
kayg:train/2026-11-03-bm11
kayg:archive/feat/imap-notes-fetch
kayg:train/2026-11-03-bm10
kayg:archive/review/sharing-pr3-review-code
kayg:archive/plan/previews-865
kayg:train/2026-11-02-bm9
kayg:archive/port/port-attachments-3
kayg:archive/port/contacts-merge
kayg:fix/motion-audit-859
kayg:train/2026-11-02-bm8
kayg:archive/scratch/bm8-prep
kayg:archive/port/port-pickers
kayg:archive/port/port-tables-2
kayg:archive/port/port-tables
kayg:archive/feat/sharing-s1-pr3-code
kayg:archive/scratch/bm7-fixed
kayg:train/2026-11-02-bm7
kayg:archive/fix/parallel-test-flakes-code-minipc01
kayg:train/2026-11-02-bm6
kayg:docs/imap-766-readonly-slice
kayg:feat/dav-recorder-v3
kayg:research/788-diagnosis
kayg:archive/fix/parallel-test-flakes
kayg:archive/perf/pr792-cold-index-edit
kayg:docs/sharing-slice-1
kayg:archive/docs/imap-766-plan-v2
kayg:feat/dav-recorder-v2
kayg:archive/docs/dav-recorder-v2-plan
kayg:spike/imap-766-s0-cases
kayg:archive/fix/undo-768
kayg:feat/dav-recorder
kayg:archive/docs/contacts-import-v2-plan
kayg:archive/feat/calendar-ical-converter
kayg:feat/contacts-import
kayg:perf/cold-growth-on-764
kayg:perf/cold-growth-on-tiering
kayg:archive/docs/tasks-writes-v2-plan
kayg:archive/docs/calendar-backend-v2-plan
kayg:archive/feat/contacts-companion-pure
kayg:feat/tasks-set-status
kayg:feat/tasks-move-reorder
kayg:archive/docs/carddav-writes-v2-plan
kayg:archive/feat/calendar-v2-s1-containment
kayg:feat/tasks-delete-promote-duplicate
kayg:archive/feat/dav-transport
kayg:archive/fix/carddav-rebuild-r1
kayg:archive/fix/carddav-retention-r1
kayg:feat/carddav-writes
kayg:perf/quota-on-tiering
kayg:perf/quota-simple
kayg:perf/cold-growth-on-quota-stack
kayg:feat/calendar-identity
kayg:feat/calendar-event-parser
kayg:feat/calendar-props-maps
kayg:port/glass-when-ring
kayg:feat/tiering-761-s1
kayg:wip/10by32-01-impl-759b-scratch-20261030
kayg:wip/6by32-01-bm3-webkit-pause-20261030
kayg:wip/10by32-01-bm3-chromium-pause-20261030
kayg:wip/6by32-01-tier-761-s1-pause-20261030
kayg:spike/imap-766-s0
kayg:wip/6by32-02-calternal-pause-20261030
kayg:wip/8by64-01-review-688-pause-20261030
kayg:wip/6by32-01-verify-744-pause-20261030
kayg:wip/6by32-01-verify-742-pause-20261030
kayg:fix/private-staging-759b
kayg:wip/6by32-01-review-748-pause-20261030
kayg:wip/6by32-01-review-733c-pause-20261030
kayg:wip/6by32-01-review-733b-pause-20261030
kayg:wip/6by32-01-perf-idle-8-pause-20261030
kayg:wip/6by32-01-perf767-base-pause-20261030
kayg:wip/6by32-01-perf767b-767-pause-20261030
kayg:wip/6by32-01-perf767b-764-pause-20261030
kayg:wip/6by32-01-perf767-767-pause-20261030
kayg:wip/6by32-01-perf767-764-pause-20261030
kayg:wip/6by32-01-lists-b-pause-20261030
kayg:wip/6by32-01-ids-verify5-pause-20261030
kayg:wip/6by32-01-ids-verify4-pause-20261030
kayg:review/calendar-month
kayg:wip/10by32-02-undo-768-20261030
kayg:test/ids-verify3
kayg:wip/10by32-02-bm3-chromium-20261030
kayg:wip/10by32-01-review-753-20261030
kayg:wip/10by32-01-perf-idle-2-20261030
kayg:wip/10by32-01-bm2-webkit2-20261030
kayg:plan/759b-staging
kayg:train/2026-10-30-bm2
kayg:review/758b-tests
kayg:review/758-tests
kayg:review/764d-tests
kayg:review/767b-tests
kayg:review/preview-2026-10-29
kayg:review/767-tests
kayg:docs/tiering-731-plan
kayg:wip/c6b02-when2
kayg:wip/c6b02-tasks-create-fix5
kayg:wip/c6b02-settings-frost-mock
kayg:wip/c6b02-settings-frost-base
kayg:wip/c6b02-settings-frost
kayg:wip/c6b02-ring
kayg:wip/c6b02-review-733d
kayg:wip/c6b02-bm1-full-a
kayg:wip/compute02-when-field
kayg:wip/compute02-webkit-lists-repro
kayg:wip/compute02-train-bl2-dev
kayg:wip/compute02-dev-green-pw164
kayg:docs/deploy-plan
kayg:review/764c-tests
kayg:review/764b-tests
kayg:review/748g-tests
kayg:review/764-tests
kayg:review/759b-tests
kayg:fix/storage-owner-liveness-642
kayg:fix/storage-fix-7
kayg:wip/compute02-storage-fix-7
kayg:review/759-tests
kayg:feat/tiering-731
kayg:wip/tiering-731-first-attempt
kayg:wip/compute02-storage-fix-1
kayg:review/754g-tests
kayg:perf/idle-honest-2
kayg:review/754f-tests
kayg:review/754e-tests
kayg:review/755-tests
kayg:review/754d-tests
kayg:review/754c-tests
kayg:review/754b-tests
kayg:review/754-tests
kayg:fix/storage-private-tmp-642-dev
kayg:wip/compute02-verify-750
kayg:test/ids-verify5
kayg:test/ids-verify4
kayg:fix/storage-exact-file-quarantine-642
kayg:wip/compute02-verify-740
kayg:perf/one-flush-save
kayg:review/calendar-static
kayg:wip/compute02-verify-737
kayg:review/indexing-ring
kayg:review/when-field-2
kayg:wip/compute02-review-733
kayg:proto/block-identity-2-review
kayg:proto/block-identity-review
kayg:wip/c6b02-tc-design
kayg:review/when-field
kayg:issue/tasks-create-ids
kayg:issue/tasks-create
kayg:review/combined
kayg:wip/compute02-review-combined
kayg:review/task-polish
kayg:review/top-buttons
kayg:review/q1-q5
kayg:train/2026-10-26-bl2
kayg:archive/wt-398h
kayg:archive/train-bm-proptest-seed
kayg:archive/integ325-293
kayg:archive/integ325-ci
kayg:archive/client-auth-review-baseline
kayg:archive/wt-sim-4853
kayg:archive/wt-sim-4811
kayg:archive/wt-448m
kayg:archive/wt-398a-allocator
kayg:issue/calendar-adr
kayg:archive/old/train/2026-10-23bk2
kayg:archive/old/train/2026-10-23bk
kayg:issue/469-mockup-9a
kayg:issue/smart-tasks-mockup
kayg:issue/469-menus
kayg:issue/652-signout-flake
kayg:issue/652-history-fix7
kayg:issue/652-history-fix6
kayg:archive/old/issue/spring-check-perf
kayg:issue/spring-check-perf
kayg:issue/659-rename-fast
kayg:archive/old/train/2026-10-21ar
kayg:issue/search-hardening-3
kayg:issue/652-history-fix5
kayg:issue/660-port-sheets-glass
kayg:issue/652-history-fix4
kayg:issue/652-history-fix3
kayg:issue/652-history-fix2
kayg:issue/652-history-flake
kayg:train/2026-10-21an
kayg:issue/settings-admin-port
kayg:issue/search-port
kayg:train/2026-10-20am
kayg:issue/storage-split-fix2
kayg:issue/storage-split
kayg:archive/old/issue/folder-ids-fix
kayg:train/2026-10-20al
kayg:archive/old/issue/folder-ids
kayg:train/2026-10-20ak
kayg:issue/tables-mockup
kayg:train/2026-10-19aj
kayg:train/2026-10-19ai
kayg:issue/469-merge-fixes
kayg:issue/sidebar-band
kayg:archive/old/issue/608-settings-spec
kayg:train/2026-10-19ah
kayg:issue/swipe-offscreen
kayg:train/2026-10-19ag
kayg:archive/old/issue/590-nlp-adr
kayg:archive/old/issue/587-event-after-write-2
kayg:train/2026-10-19af
kayg:archive/old/issue/587-event-after-write
kayg:issue/469-pill-room-c
kayg:issue/469-pill-room
kayg:train/2026-10-19ae
kayg:issue/469-narrow-handle
kayg:issue/397-ios-swipe-2
kayg:archive/old/issue/adrs-photos-files-tasks
kayg:issue/469-desk-fixes
kayg:archive/old/issue/auto-update-adr
kayg:issue/469-filter-gap
kayg:issue/469-port-1b
kayg:issue/442-opacity-flake
kayg:archive/397-ios-swipe-2
kayg:issue/327-web-fast-3
kayg:archive/old/issue/469-port-1
kayg:issue/469-port-1
kayg:issue/327-web-fast-2
kayg:archive/old/issue/327-web-fast
kayg:archive/old/issue/425-stt-bench-vq1
kayg:archive/old/issue/445-no-bleed
kayg:archive/old/issue/400-swipe-mockup-2
kayg:issue/426-client-review
kayg:issue/426-client-2
kayg:issue/426-client
kayg:archive/old/issue/476-safe-area-lab
kayg:archive/445-top-light-2
kayg:archive/old/issue/445-top-light
kayg:issue/471-blocks-mockup
kayg:archive/old/issue/448-links-app-2
kayg:archive/old/issue/448-links-app
kayg:archive/old/issue/398-cold-links
kayg:issue/434-442-small-2
kayg:issue/434-442-small
kayg:archive/old/issue/398-unload
kayg:archive/old/issue/445-bottom-band
kayg:issue/445-bottom-band
kayg:issue/351-rem-3
kayg:archive/312-views-mockup
kayg:issue/351-rem-2
kayg:issue/351-rem
kayg:archive/old/issue/327-first-note
kayg:issue/395-width-phone
kayg:archive/old/issue/327-typing
kayg:issue/396-ios-edges
kayg:issue/397-ios-swipe
kayg:archive/398-allocator
kayg:archive/old/issue/327-notes-index
kayg:issue/345-347-contrast-empty-2
kayg:archive/old/issue/44-arenas
kayg:issue/44-arenas
kayg:archive/integ325
kayg:archive/old/issue/327-perf-harness
kayg:archive/old/issue/44-thp
kayg:issue/44-thp
kayg:archive/old/issue/285-sticky-pills
kayg:archive/old/issue/44-trim-3
kayg:issue/44-trim-3
kayg:archive/old/issue/165-cards
kayg:archive/old/issue/165-picker-2
kayg:archive/old/issue/212-multi-select
No reviewers
Labels
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
kayg/calternal!688
Loading…
Reference in a new issue
No description provided.
Delete branch "issue/686-netbird-deploy"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Deploys now run from the orchestration host: build-slot.sh builds on compute01, and image transfer plus target operations use configurable NetBird commands relayed from that host. Staging defaults move to compute01; production remains unchanged until an operator runs TARGET=cloud. Rollback uses the same transport. Snapshot, health-wait and automatic rollback behavior is preserved.
Validation: bash -n and shellcheck on compute01 passed. Staging dry-run and live TARGET=next relay deploy passed: origin/dev image
a779a46f42built on compute01, image relayed through orchestration, data snapshotted, then healthy startup. Preview public HTTP status is 200. Public next routing is separately blocked because its EndpointSlice is Argo-managed, contrary to the job plan; no GitOps policy/source was changed. No production deploy was run.Related #686. Do not merge as part of this migration job.
CI runs 485 (unchanged dev smoke) and 486 (this PR) execute on migrated runners. Server passed; web stops at existing bundle budget failures (308 > 260 KB total JS, 22.7 > 22.1 KB initial CSS, 255 > 240 KB initial JS); Smoke run 485 finished: server success, Rust success, web failure at the bundle gate. Duplicate run 486 was cancelled after the same web failure to give the smoke run capacity. This PR changes deployment text/scripts only and does not change budgets.
Independent review started for PR #688. Local review is detached at base
3d0c6e33d5; PR branch issue/686-netbird-deploy, headbc25169a3e. I will inspect every commit and changed deploy script, run local checks and throwaway-target probes, and record ranked findings. No push, merge, deploy or production contact.Independent review finding: P1 — A full disk or switch error leaves the existing service stopped
deploy/next/remote.sh:97-101stops the service beforesnapshotand retagging. The snapshot copy (:59), LAST write (:60), and new-image tag (:99) are outside the health failure branch;set -eexits without restarting the old service. There is no EXIT/error recovery trap. This is inherited and retained by this PR.Evidence:
python3 /tmp/review-688-evidence/probe.pyruns the unchanged script with local command mocks, isolated temporary userdata, and injected failures.disk-full: copy returns 1 withcp: simulated No space left on device; exit 1, old service remains stopped, no health request.tag-fails: new tag operation returns 1; exit 1, old service remains stopped. Add transactional recovery covering every operation after stop; avoid unnecessary userdata copies for code deploys.Independent review finding: P1 — Automatic rollback deletes and replaces live userdata
deploy/next/remote.sh:112always restores the snapshot on failed health.restore_last_snapshot(:67-70) explicitly runsrm -rf "$DATA"and replaces the data directory. Snapshot retention also deletes withrm -rf(:63). This violates this review's requirement that deploys never delete or manipulate Home/state beyond app operations; daily backups already exist. A failed health response can overwrite writes made after new startup and cannot safely reverse external/multi-root changes. This is inherited, not introduced by the relay.Evidence: the
health-failslocal probe executes automatic rollback and recordsrm -rf <temporary-root>/data.restoringandrm -rf <temporary-root>/data. Keep production code rollback separate from data restoration; require independently planned reversible migrations rather than implicit data replacement.Independent review finding: P1 — The artifact is not bound to the exact dev revision
deploy/next/relay-deploy.sh:23-30compares local HEAD with the build checkout, not an authoritative dev SHA.:21-31skips even that comparison for an explicit tag, while:36,47saves and loads a mutable tag with no image digest/revision inspection.deploy/Containerfilehas no source revision label. A matching feature checkout, stale checkout, or unrelated image with a plausible SHA tag is accepted for cloud. The build and later save are also separate remote calls, so another build can overwrite the tag between them.Evidence:
DRY_RUN=1 TARGET=next NEXT_HOST=throwaway.invalid REMOTE_CMD=/bin/false deploy/next/deploy.shplans a build of PR HEADbc25169a3e5c4076d3e4e1dda7c91fab1e3cf98f, despite the API dev SHA being3d0c6e33d588335c745a660163bf8bf5cdc8412d. The same command withreview688has no BUILD/check step at all. Resolve and record the full dev SHA, build immutable content from that revision, and verify image identity through transfer and running-container health.Independent review finding: P1 — Health can report success after service start failed
deploy/next/remote.sh:83-85does not check the return ofsysd start. Becausestart_and_checkis invoked in anif(:101), Bash suppresses errexit inside the function; a successful curl masks a failed systemctl start.healthy(:74-80) checks any HTTP success at a fixed address, not service state or deployed revision. There is no pre-deploy health check. This is inherited.Evidence:
start-failslocal probe makes systemctl start return 1 and HTTP return 0. The script exits 0, printshealthy: new is live, and the simulated service remains stopped. This models a stale or unrelated responder. Check the start status explicitly and verify service/container identity as well as HTTP health, before and after switching.Independent review finding: P1 — Predictable root-written unit staging file is unsafe
deploy/next/relay-deploy.sh:37,48uses root'stee /tmp/$NAME.container;remote.sh:42installs that shared path as a root-owned operation without checking ownership, symlinks or contents. Any local user can precreate the predictable path as a symlink before transfer or replace a writable staged unit before installation. This gives a local user a root-file overwrite opportunity or changes the container unit. The predictable file existed before; the new relay retains it and adds no isolation or file-mode controls.Evidence: the dry-run prints
UNIT throwaway.invalid: tee /tmp/calternal-next.container.teefollows symlinks and does not create exclusive private storage. Use a private, root-owned mode-0700 transaction directory created with mktemp and an exclusive mode-0600 file, carried through the same locked transaction. No real target or shared /tmp unit was used in this review.Independent review finding: P2 — Re-running deploy erases the usable rollback revision
deploy/next/remote.sh:94-95unconditionally overwrites rollback with current, even if the requested artifact is already current. A successful retry converts rollback from the previous working build to the same build currently deployed. Snapshot names also have only second precision (:58), andcpnests data into an existing snapshot on a same-second retry. There is no deployment lock across relay calls (relay-deploy.sh:46-49), shared unit staging, current/rollback tags or LAST, so concurrent operations can interleave these mutations. This is inherited switch behavior exposed by the relay.Evidence:
rerunlocal probe runs deploy twice with current=old and requested=new. Both exit 0; final tags are current=new, rollback=new. Its deliberately fixed timestamp also reproduces the snapshot collision. Compare immutable current/requested identities and return without changing rollback on retry; serialize the complete upload/switch transaction and use unique snapshot names if snapshots remain elsewhere.Independent review finding: P2 — Failure logging does not guarantee secret redaction
deploy/next/remote.sh:107exports the last 20 service journal lines and excludes only lines containing the wordtoken. Secrets named passwords, keys, authorization headers or sensitive app content are not excluded. The units load a secrets env file (deploy/cloud/calternal.container:20-21, next equivalent), but the deploy path neither provisions nor validates its mode. This is inherited. No actual secret was read or printed in this review.Evidence: the
health-failsprobe uses only a synthetic journal line,synthetic credential value: REVIEW688_FAKE_VALUE; the script forwards it verbatim. Keep raw service journals on the target; return bounded non-sensitive deployment diagnostics. Require restrictive modes for secret files in the setup contract.Independent review finished. Verdict: fix first.
PR #688 independent review
Verdict: fix first. Reviewed the description and the sole commit,
bc25169a3e5c4076d3e4e1dda7c91fab1e3cf98f, including all ten changed files. Forgejo reported dev base3d0c6e33d588335c745a660163bf8bf5cdc8412d. Findings below distinguish new relay problems from inherited switch-script problems: both matter before using this production path. No production connection, deployment, push, merge or commit was performed.P1 — A full disk or switch error leaves the existing service stopped
deploy/next/remote.sh:97-101stops the service beforesnapshotand retagging. The snapshot copy (:59), LAST write (:60), and new-image tag (:99) are outside the health failure branch;set -eexits without restarting the old service. There is no EXIT/error recovery trap. This is inherited and retained by this PR.Evidence:
python3 /tmp/review-688-evidence/probe.pyruns the unchanged script with local command mocks, isolated temporary userdata, and injected failures.disk-full: copy returns 1 withcp: simulated No space left on device; exit 1, old service remains stopped, no health request.tag-fails: new tag operation returns 1; exit 1, old service remains stopped. Add transactional recovery covering every operation after stop; avoid unnecessary userdata copies for code deploys.P1 — Automatic rollback deletes and replaces live userdata
deploy/next/remote.sh:112always restores the snapshot on failed health.restore_last_snapshot(:67-70) explicitly runsrm -rf "$DATA"and replaces the data directory. Snapshot retention also deletes withrm -rf(:63). This violates this review's requirement that deploys never delete or manipulate Home/state beyond app operations; daily backups already exist. A failed health response can overwrite writes made after new startup and cannot safely reverse external/multi-root changes. This is inherited, not introduced by the relay.Evidence: the
health-failslocal probe executes automatic rollback and recordsrm -rf <temporary-root>/data.restoringandrm -rf <temporary-root>/data. Keep production code rollback separate from data restoration; require independently planned reversible migrations rather than implicit data replacement.P1 — The artifact is not bound to the exact dev revision
deploy/next/relay-deploy.sh:23-30compares local HEAD with the build checkout, not an authoritative dev SHA.:21-31skips even that comparison for an explicit tag, while:36,47saves and loads a mutable tag with no image digest/revision inspection.deploy/Containerfilehas no source revision label. A matching feature checkout, stale checkout, or unrelated image with a plausible SHA tag is accepted for cloud. The build and later save are also separate remote calls, so another build can overwrite the tag between them.Evidence:
DRY_RUN=1 TARGET=next NEXT_HOST=throwaway.invalid REMOTE_CMD=/bin/false deploy/next/deploy.shplans a build of PR HEADbc25169a3e5c4076d3e4e1dda7c91fab1e3cf98f, despite the API dev SHA being3d0c6e33d588335c745a660163bf8bf5cdc8412d. The same command withreview688has no BUILD/check step at all. Resolve and record the full dev SHA, build immutable content from that revision, and verify image identity through transfer and running-container health.P1 — Health can report success after service start failed
deploy/next/remote.sh:83-85does not check the return ofsysd start. Becausestart_and_checkis invoked in anif(:101), Bash suppresses errexit inside the function; a successful curl masks a failed systemctl start.healthy(:74-80) checks any HTTP success at a fixed address, not service state or deployed revision. There is no pre-deploy health check. This is inherited.Evidence:
start-failslocal probe makes systemctl start return 1 and HTTP return 0. The script exits 0, printshealthy: new is live, and the simulated service remains stopped. This models a stale or unrelated responder. Check the start status explicitly and verify service/container identity as well as HTTP health, before and after switching.P1 — Predictable root-written unit staging file is unsafe
deploy/next/relay-deploy.sh:37,48uses root'stee /tmp/$NAME.container;remote.sh:42installs that shared path as a root-owned operation without checking ownership, symlinks or contents. Any local user can precreate the predictable path as a symlink before transfer or replace a writable staged unit before installation. This gives a local user a root-file overwrite opportunity or changes the container unit. The predictable file existed before; the new relay retains it and adds no isolation or file-mode controls.Evidence: the dry-run prints
UNIT throwaway.invalid: tee /tmp/calternal-next.container.teefollows symlinks and does not create exclusive private storage. Use a private, root-owned mode-0700 transaction directory created with mktemp and an exclusive mode-0600 file, carried through the same locked transaction. No real target or shared /tmp unit was used in this review.P2 — Re-running deploy erases the usable rollback revision
deploy/next/remote.sh:94-95unconditionally overwrites rollback with current, even if the requested artifact is already current. A successful retry converts rollback from the previous working build to the same build currently deployed. Snapshot names also have only second precision (:58), andcpnests data into an existing snapshot on a same-second retry. There is no deployment lock across relay calls (relay-deploy.sh:46-49), shared unit staging, current/rollback tags or LAST, so concurrent operations can interleave these mutations. This is inherited switch behavior exposed by the relay.Evidence:
rerunlocal probe runs deploy twice with current=old and requested=new. Both exit 0; final tags are current=new, rollback=new. Its deliberately fixed timestamp also reproduces the snapshot collision. Compare immutable current/requested identities and return without changing rollback on retry; serialize the complete upload/switch transaction and use unique snapshot names if snapshots remain elsewhere.P2 — Failure logging does not guarantee secret redaction
deploy/next/remote.sh:107exports the last 20 service journal lines and excludes only lines containing the wordtoken. Secrets named passwords, keys, authorization headers or sensitive app content are not excluded. The units load a secrets env file (deploy/cloud/calternal.container:20-21), but the deploy path neither provisions nor validates its mode. This is inherited. No actual secret was read or printed in this review.Evidence: the
health-failsprobe uses only a synthetic journal line,synthetic credential value: REVIEW688_FAKE_VALUE; the script forwards it verbatim. Keep raw service journals on the target; return bounded non-sensitive deployment diagnostics. Require restrictive modes for secret files in the setup contract.Coverage and limits
remote.sh:108).deploy/entrypoint.sh:3-8). The entrypoint requires explicit creation opt-in for new storage. This review does not certify arbitrary dev revisions' migrations. Userdata snapshot and deletion are unnecessary here and violate the requested deploy contract. Full-disk failure was injected, not produced by filling a real disk.netbird ssh(transport.sh:5-6); cloud defaults to a NetBird hostname (deploy/cloud/target.env:5). REMOTE_CMD can bypass that policy. No firewall or sshd changes are included. Staging adds a loopback binding at127.0.0.1:8080alongside its changed LAN binding (calternal-next.container:12-13); cloud's LAN-only port is unchanged. Target SSH is unrestricted root by default, not a constrained deployment account; image runtime is rootless. No ACL/sudo provisioning is supplied, so least privilege is not demonstrated.README.md:57-62, cloud selection:88-94), but retry corrupts its previous-image pointer. Success logs identify a caller-selected tag, not full source SHA/image digest. No deploy-specific tests were found in the tree or added by the sole PR commit.bash /tmp/review-688-evidence/gate.sh,python3 /tmp/review-688-evidence/probe.py, andpython3 /tmp/review-688-evidence/relay-probe.py. The probe source, event traces and run JSON remain outside the repo under/tmp/review-688-evidence,/tmp/review688-probe-65j0p1bz, and the relay directories named in their output. Mocks cover command ordering and status handling, not real Podman, systemd, disk exhaustion, NetBird ACLs or public routing. No build or production access was needed.Final gate
Reviewed head:
bc25169a3e5c4076d3e4e1dda7c91fab1e3cf98f.Verbatim gate output (passing reproduction probes mean the defects were reproduced, not that production deployment is safe):
The only workspace addition is this uncommitted review report. No product changes, commits, pushes, merges, deployments, issue closure or production connections.
Starting deploy-fix-688 on branch fix/deploy-688. Base after merging current origin/dev:
e4d8ebf309. Read all seven P1/P2 findings and comment 66964. I will replace userdata handling with code-only recovery, pin origin/dev artifacts, lock upload and switch together, and commit mock regression tests. No production contact, merge or deploy.Finding 1, stopped service after failure: remote.sh now prepares uploads, checksum checks, trial tags and unit files before stop, and arms EXIT/HUP/INT/TERM recovery around the switch. Local committed-probe candidates passed disk-full, tag-fails, switch-tag-fails, reload-fails, unit-move-fails and signal scenarios: each returned failure with the old service running. No production connection.
Finding 2, userdata replacement: removed all snapshots, LAST markers, retention deletion and restore code. Deploy and rollback only change image tags and the quadlet. The health-fails probe passed with the old image restarted, and command traces contain no Home/state or snapshot operations. The relay probe rejects rollback --data.
Finding 3, dev provenance: relay resolves full fetched origin/dev; builder archives that exact tree, labels the image and compiles the revision into /healthz, then saves by immutable image ID with a transfer checksum. Target validates the checksum, ID and OCI revision. Local label-mismatch, id-mismatch and checksum-mismatch probes passed; next/cloud reject mutable tags and non-dev SHAs before transport. Podman documents bare image IDs, so checks normalize the prefix: https://docs.podman.io/en/latest/markdown/podman-image-inspect.1.html .
Finding 4, health: start_and_check explicitly checks systemctl start, active state, running container ID and the compiled health revision. The same checks run before deploy. start-fails, inactive-systemd, wrong-container-image, stale-response and pre-unhealthy probes passed. Safest first-use choice: an existing image without revision metadata fails closed and needs supervised bootstrap; documented in README. The fixes must land on dev first.
Finding 5, unit staging: one target flock now covers precheck, the entire upload and switch. Staging is a private mktemp directory on the unit filesystem, with 0700 directory and 0600 files; no shared /tmp unit path. The load mock checks actual staging modes. Unit installation and atomic rename failure probes passed; neither leaves the old service down.
Finding 6, retries and concurrency: immutable current/requested IDs are compared before any service or rollback-pointer change. The rerun probe passed with rollback still pointing at the old image and no second stop/start/tag. All target operations use the same per-service flock. A further concurrent-upload probe is being added to verify serialization directly.
Finding 7, logs and secrets: removed journal forwarding entirely; target diagnostics use fixed text and validated SHA/image IDs. Secret files must be regular, not symlinks, owned by calternal and mode 0400 or 0600. open-secret, missing-secret and symlink-secret probes passed without a stop. Synthetic credential text is asserted absent from output across all probes. Required modes and local-only diagnostics are documented.
Finished deploy-fix-688. Replacement PR: #733 (supersedes #688; neither PR was merged or closed).
Branch: fix/deploy-688. Head:
63e1ce5a7d. Base includes current origin/dev at3d0c6e33d5. Pushed only this new branch. No production contact, deployment or userdata operations.All seven findings now have fixes and committed regression coverage. Additional probes verify real flock serialization, exact Git archive contexts despite dirty/untracked files, immutable-ID saving, and old-image startup when recovery retagging also fails. Evidence and logs were not committed.
Safest first-use choice: a current image without OCI revision metadata and a matching compiled health SHA is refused. Such a baseline needs supervised bootstrap after these fixes land on dev. Tests use local mocks; real Podman, systemd and NetBird first-use checks remain supervised work, not a deployment performed by this job.
Verbatim output from bash deploy/tests/gate.sh at the head above:
The targeted Rust HTTP health test also passed through build-slot with CALTERNAL_REVISION=bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:
cargo fmt --all -- --check passed. The worktree is clean.
Starting second deploy fix round in /home/agent/wt/deploy-fix, branch fix/deploy-688, base
63e1ce5a7d. I will reproduce the recovery restart and EnvironmentFile mode-check findings with mocks, add regression tests, document supervised bootstrap, and run bash -n, shellcheck, deploy tests and dry-run. No production contact, merge or deploy.Findings reproduced against
63e1ce5awith committed-test candidates and isolated local mocks. P1: persistent daemon-reload failure, persistent unit rename failure, stop returning an error after stopping, and post-stop Podman error all exit with the old service stopped and zero restart calls. P2: spaced EnvironmentFile directives in either the installed or replacement unit allow a synthetic mode-0644 file through and deploy succeeds. The revised systemd mock keeps the loaded generated definition separate from the quadlet. Evidence is /tmp/deploy-733-red.log plus targeted repro output on 8by64-compute01; no production contact. Fix in progress: unconditional best-effort recovery start with independent preparation steps and retained recovery units on failure; whitespace-aware, fail-closed secret path validation.Second deploy fix round finished. Branch fix/deploy-688, head
a6a50fe526, pushed successfully. PR #733 reports that exact head. Base was63e1ce5a7d.Safest syntax choice: unsupported path forms are explicitly refused rather than guessed. The README documents the accepted subset and conservative handling of optional files and reset directives. Parsing references were read from the primary Podman Quadlet and systemd syntax documentation through Firecrawl.
Gates:
timeout -k 10 360 bash deploy/tests/gate.shexited 0. The additionalbash -n deploy/*.sh deploy/next/*.sh deploy/tests/gate.shandgit diff --checkalso exited 0. The working tree is clean. All tests use local mocks; next/cloud dry-runs use an invalid destination and /bin/false transport. No production contact, merge or deployment. No evidence or artifacts were committed.Gate output verbatim:
Starting deploy-fix-3 on branch fix/deploy-688, base
a6a50fe526. Reading the third review and probe scripts, then replacing the switch with an explicit state machine and exhaustive failure injection. Local tests and dry-run only; no production access, merge or deploy. The job explicitly requires pushing fix/deploy-688 and commenting on PR #733; I will follow that specific branch instruction.Confirmed third-review root causes at
a6a50fe5: recovery copies restore.container after stopping and retagging, so ENOSPC can leave the loaded candidate definition; retry checks only digest, so a different digest with the same revision overwrites rollback. The reviewer host SSH timed out; retrying and reading the complete probe attached in comment 67040 as fallback. Fix will pin immutable image identities before stop and model all switch transitions explicitly.Reproduced both review findings with the independent source from comment 67040 and corrected safety assertions: disk-full-after-start exited 1 with candidate digest 2222 running; same-revision exited 0 after one stop and replaced the prior rollback. Added both as regression tests. The state machine now prepares digest-pinned main/recovery units and an independent loaded fallback before stopping; recovery consumes the prepared unit by rename and never starts an uncertain candidate definition. Same-SHA deploys preserve service and rollback even with another digest. Both new regression cases now pass. Before/after command-boundary failure injection is running, including recovery paths and userdata sentinels. Bootstrap choice: keep the existing refusal for unlabelled baselines and test that refusal, rather than automate the supervised maintenance procedure. Reviewer-host access is blocked: hostname SSH timed out then failed DNS; a bounded retry to its last resolved non-production IP also timed out. The complete attached probe source and findings in comments 67031/67032/67040/67041 were read instead. No production access.
Additional state-machine checks: daemon-reload alone does not prove that a generator replaced the loaded service definition. Recovery now checks the loaded ExecStart image before starting the primary, and uses the preloaded old-digest fallback if it is stale. Added passing stale-recovery-definition (refuse before stop), stale-primary-recovery (old digest through fallback), and post-start-unit-rename (old digest through fallback) probes. The real installed Quadlet generator also compiles both pinned services, with separate CID files and a Conflicts relation. Retained recovery material is refused and preserved; the retained-recovery probe passes. A tag write that takes effect before returning an error is treated as the commit point only after candidate health was verified; exhaustive before/after command injection covers this boundary. The final gate is running from a fixed source version; earlier exploratory runs are not being reported as a final pass.
Gate finding: bash -n accepts one script followed by its positional arguments, so the previous wildcard invocation did not parse every deploy script. The gate now loops over each file; the explicit all-files syntax check passed, and the final suite has started with that corrected gate. The exhaustive test oracle independently records command return status and requires a successful candidate revision validation before accepting a committed image; userdata snapshots compare modes, ownership and timestamps as well as bytes. No switch implementation changes were made during this final test strengthening.
deploy-fix-3 finished. Branch: fix/deploy-688. Base:
a6a50fe526. Pushed head:8dc52337a5.Review this commit. Forgejo reports PR #733 at this head.
Validation: /usr/local/bin/build-slot.sh bash deploy/tests/gate.sh exited 0. The exhaustive fixtures use at most three independent workers inside that one slot. Final all-files syntax, ShellCheck and staged whitespace checks also passed. Gate output verbatim:
Limits and choices: the automated bootstrap tests cover refusal, not the separate supervised maintenance procedure. Real Quadlet generation and the local systemd ExecStart field format were checked; the command mocks do not certify live target Podman/systemd/NetBird behavior. Persistent infrastructure failure retains repair material and reports failure; power loss and SIGKILL still require local repair.
The reviewer host could not be read: bounded NetBird SSH attempts timed out or failed DNS, including a final retry. I read comments 67031, 67032, 67040 and 67041, including the complete independent probe source, and reproduced both findings locally before fixing them.
Only fix/deploy-688 was pushed. No production connection, merge or deploy. No issue was closed. The commit contains product scripts, tests and lasting operational documentation only; evidence and gate output stay in issue comments.
Starting deploy-fix-6 on staging 8by64-compute01. Base
d2ade1fb93, fetched fix/deploy-688-round5. Working branch fix/deploy-688-round6: the job's hard rule requires a new branch and PR because this forge ignores pushes to an open PR branch. I will link the replacement here and keep #748 open.Scope: scripted bootstrap with all image/unit writes and free-space checks before stop; missing first predecessor; worker umask; FIFO producer race. Gates: bash -n, shellcheck, all deploy tests, real staging bootstrap/full-disk/deploy/same-SHA/rollback rehearsal. No production contact, merge, or production deploy. Evidence stays untracked and in issue comments.
Finding → fix → test, round 6 in progress:
P1 bootstrap: README steps 6–8 stop before tagging; the review's persistent ENOSPC model strands recovery. Replacing the manual procedure with
deploy.sh bootstrap, routed through the existing detached lock, free-space admission, runtime reserves, scratch generator, prepared unit/pointer files and recovery. The running legacy digest is explicitly recorded before stop; only that digest permits status-only health, without fabricating a revision. New images still require the dev SHA. README becomes a dry-run and one command.P2 first update: switch_prepare required
:rollbackeven when a healthy labelled baseline had no predecessor. The image record now represents an absent predecessor as-; commit still records the old baseline as the real predecessor. Regression paths exercise the first update and its failures.P2 worker permissions: the parent umask does not cross systemd-run. Worker now sets umask 077 itself. Real user-systemd tests under UMask=0002 pass and verify receipt 0600 and enclosing directory 0700.
P2 FIFO race: shell
>recreates the path after an early receiver exit. Producer now opens without O_CREAT, refuses symlinks and verifies the opened descriptor is a FIFO. A handshake forces receiver completion before producer open; mocks and real user systemd both pass with no retained artifact from a 262144-byte payload. Existing disconnect, abandoned upload, partial receipt, failure result and lock tests pass as well.Evidence: .job-evidence/round6/transport.log (not committed). Full fault injection and real app rehearsals remain to run.
Real rehearsal progress on staging only: installed next normal deploy, same-SHA redeploy (same MainPID), and rollback pass. Disposable real Podman/systemd bootstrap from an unlabelled image passes; failed candidate health recovers the legacy image and original unit bytes. Both full-disk bootstrap admission and exhaustion after admission retain the exact old MainPID at zero available blocks.
The extra legacy rollback run correctly refused free-space admission in the 1 GiB fixture: legacy image size 115693123 bytes requires about 523 MiB headroom while about 461 MiB remained. This is a fixture sizing failure, not a service outage or bypass. The old candidate stayed healthy; cleanup completed. First-attempt logs are retained. Repeating the entire rehearsal with a dedicated 2 GiB filesystem, including explicit proof that the after-admission exhaustion hook completed before proceeding.
Round-six real staging rehearsal: PASS. All 12 rows below ran on 8by64-compute01 using real rootless Podman, real user systemd, the installed Quadlet generator and the real app. Existing A/B images and the original unlabelled image were reused; this script-only change requires no app rebuild. Normal deploy/rollback used the installed next service. Bootstrap and ENOSPC used a separate disposable real app and a bounded dedicated 2 GiB ext4 image store; no shared filesystem was filled.
Both full-disk rows verified zero available blocks with the original MainPID continuously alive. The after-admission hook separately recorded zero blocks before image-load/tag/preparation could complete. Failed candidate health restored the exact unlabelled digest and original unit bytes. Explicit legacy rollback verified status and digest; normal deploy subsequently removed the legacy exception. Staging finishes at A with rollback B and byte-identical original installed unit; mount roots match entry. Disposable service/store/data were cleaned. Per-run logs and rehearsal source are retained untracked and will be attached with the final gate output.
Full gate now running through one local build slot, with three injection workers. No production contact, merge, or production deploy.
Additional finding during this fix's verification: the first absence-handling implementation conflated an image-inspection error with an absent rollback tag. A targeted injection at the rollback image inspection, followed by candidate health failure, reproduced old-image recovery with the older predecessor erased to
-. Evidence: .job-evidence/round6/predecessor-red.log. This did not affect the earlier real staging rehearsal, which had no runtime inspection errors.I stopped only this job's recorded timeout PID 1452960 after verifying its exact command and owner. The partial sweep is retained with exit 143; it is not a full gate pass. No other process was signalled.
Fix: distinguish Podman's explicit image-exists result 1 (absent) from result 0 (present, inspection must succeed) and runtime errors (abort before switch). This keeps real absence supported while preserving an existing predecessor on errors. Targeted before/after injections and explicit runtime/inspection error tests are being run, then the full real rehearsal and gate will be repeated from the start.
After correcting the predecessor inspection/absence edge case, the entire real staging rehearsal was repeated from the start and all 12 rows passed again. Actual Podman image-exists results were explicitly checked: present image = 0, absent image = 1. Both zero-block bootstrap cases keep the old MainPID unchanged; failed bootstrap health recovers the exact unlabelled image; legacy rollback, normal deploy, same-SHA and normal rollback all pass. Staging again returns to A with rollback B, original installed unit bytes and unchanged mount identities. Disposable resources are removed.
Targeted predecessor existence and inspection failures before/after each command now preserve the older rollback identity through failed health. A fresh full gate is running from the beginning. The prior interrupted partial sweep remains recorded as exit 143 and is not claimed as a pass. Final evidence will include both the reproduced edge case and its passing tests, plus every rehearsal run log.
Resuming after host reboot on fix/deploy-688-round5, base/head
768dbacbb6. The coordinator already pushed the WIP to PR #748; this task explicitly requires updates only to that branch and PR, overriding the earlier generic new-branch instruction. Rechecking staging, runner and fixtures; rerunning the interrupted full gate and real staging rehearsals. No production access or merge. Evidence remains untracked.Post-reboot evidence on 8by64-compute01: boot ID 3e68dc6c-c253-49bd-a643-7dc507439df6. calternal-next.service active under user systemd; container running image sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb. Health revision
8dc52337a5, status ok. Saved pointer current matches that image; rollback sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108. Both forgejo-runner.service and forgejo-runner-web.service active since 06:43:49 CET with NRestarts=0. No leftover disposable round6/round6-extra stores. A/B archives and bundle fixtures remain present. Root fixture access works. Previous gate stopped without gate.exit at bootstrap-health-fails after-command 80/106; not a completed gate. Additional real regression evidence directory contains fixture symlinks only; those rehearsals have not run. Rerunning all gates and real rehearsals through one build slot, without changing production or filling a shared filesystem.Fresh post-reboot bootstrap rehearsal: all 12 rows PASS, command exit 0. Real next deploy, same-SHA with same MainPID, rollback, unlabelled bootstrap, legacy rollback, failed bootstrap health recovery, normal post-bootstrap deploy/same-SHA/rollback and final restoration passed. Both full-disk bootstrap cases (before admission and after admission before image load) observed zero available blocks and the same original service MainPID. Staging returns to A/rollback B, byte-identical installed unit and unchanged mount roots. Per-run logs are retained untracked and will be attached with final gates. Additional real regression rehearsal is running, then the interrupted exhaustive gate will rerun from its start.
{"case": "real staging normal deploy", "outcome": "PASS", "current": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "rollback": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "revision": "
5b0dd8d009", "mount_roots": "unchanged"}{"case": "real staging same SHA, same MainPID", "outcome": "PASS", "current": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "rollback": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "revision": "
5b0dd8d009", "mount_roots": "unchanged"}{"case": "real staging rollback", "outcome": "PASS", "current": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "rollback": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "revision": "
8dc52337a5", "mount_roots": "unchanged"}real Podman image-exists present=0 absent=1: PASS
{"case": "bootstrap full filesystem admission", "outcome": "PASS", "current": "sha256:983a8a530cb4330dadeb8c3f125ea2cfec9b242ae21c571d4fc6e964b5a1e17d", "old_service": "same MainPID", "available_blocks": 0}
{"case": "bootstrap full filesystem after admission", "outcome": "PASS", "current": "sha256:983a8a530cb4330dadeb8c3f125ea2cfec9b242ae21c571d4fc6e964b5a1e17d", "old_service": "same MainPID", "available_blocks": 0}
{"case": "bootstrap candidate failure recovers unlabelled image", "outcome": "PASS", "current": "sha256:983a8a530cb4330dadeb8c3f125ea2cfec9b242ae21c571d4fc6e964b5a1e17d", "rollback": "-", "unit_bytes": "original"}
{"case": "bootstrap from unlabelled running image", "outcome": "PASS", "current": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "rollback": "sha256:983a8a530cb4330dadeb8c3f125ea2cfec9b242ae21c571d4fc6e964b5a1e17d", "revision": "
8dc52337a5"}{"case": "rollback to recorded legacy image", "outcome": "PASS", "current": "sha256:983a8a530cb4330dadeb8c3f125ea2cfec9b242ae21c571d4fc6e964b5a1e17d", "rollback": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "health": "status and exact digest"}
{"case": "normal deploy after bootstrap removes legacy exception", "outcome": "PASS", "current": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "rollback": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "revision": "
5b0dd8d009"}{"case": "same SHA after bootstrap", "outcome": "PASS", "current": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "rollback": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "old_service": "same MainPID"}
{"case": "normal rollback after bootstrap", "outcome": "PASS", "current": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "rollback": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "revision": "
8dc52337a5"}{"case": "final staging state", "outcome": "PASS", "current": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "rollback": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "revision": "
8dc52337a5", "mount_roots": "unchanged"}round six real staging rehearsal: PASS
Fresh additional real staging rehearsal: 13 rows PASS, command exit 0. Together with the bootstrap rehearsal, all 25 rows now pass after reboot. Persistent ENOSPC remained at zero blocks at prepared, candidate_started and recovery_health; the filler stayed until recovery was verified. Real invalid Quadlet output loss recovered via the precompiled static unit. Terminating only this job's recorded loopback SSH client after stop did not abort detached failed-health recovery; result 001 was persisted and the original A image/unit with rollback B restored. Fixtures cleaned. Full gate has restarted from the beginning in one local build slot. Evidence below; final archive and verbatim gates follow on completion.
{"case": "real staging deploy", "outcome": "PASS", "current": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "rollback": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "revision": "
5b0dd8d009", "mounts_and_data_roots": "unchanged"}{"case": "real staging same SHA", "outcome": "PASS", "current": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "rollback": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "revision": "
5b0dd8d009", "mounts_and_data_roots": "unchanged"}{"case": "real staging rollback", "outcome": "PASS", "current": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "rollback": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "revision": "
8dc52337a5", "mounts_and_data_roots": "unchanged"}{"case": "real full receipt filesystem cleans partial upload", "outcome": "PASS", "current": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "rollback": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "revision": "
8dc52337a5", "mounts_and_data_roots": "unchanged"}{"case": "real staging invalid generator input", "outcome": "PASS", "current": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "rollback": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "revision": "
8dc52337a5", "mounts_and_data_roots": "unchanged"}{"case": "real staging failed candidate recovery", "outcome": "PASS", "current": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "rollback": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "revision": "
8dc52337a5", "mounts_and_data_roots": "unchanged"}{"case": "isolated store paths verified", "outcome": "PASS", "graph_root": "/home/calternal/.deploy-round6-extra/graph", "run_root": "/run/user/30033/calternal-round6-extra-run"}
{"case": "isolated real Podman deploy and rollback", "outcome": "PASS", "current": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "rollback": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "store": "dedicated ext4"}
{"case": "persistent full image store through recovery", "outcome": "PASS", "current": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "rollback": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "zero_free_blocks_at": ["candidate_started", "prepared", "recovery_health"], "filler": "retained until recovery verified"}
{"case": "free-space margin refuses next deploy", "outcome": "PASS", "current": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "rollback": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "old_service": "same MainPID throughout", "available_bytes": 0}
{"case": "final real staging check", "outcome": "PASS", "current": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "rollback": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "revision": "
8dc52337a5", "mounts_and_data_roots": "unchanged"}real staging rehearsal: PASS
{"case": "real generator output loss and static fallback", "outcome": "PASS", "current": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "rollback": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "revision": "
8dc52337a5", "details": "Actual invalid Quadlet removed primary generator output; a precompiled static unit served A. Original unit and primary A restored under the deploy lock."}{"case": "real SSH disconnect mid-switch", "outcome": "PASS", "current": "sha256:60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb", "rollback": "sha256:26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108", "revision": "
8dc52337a5", "details": "Killed the recorded loopback SSH client after the old service stopped; released candidate into failed health. PID 1 completed A recovery and persisted result 001 with original unit bytes and rollback B."}real generator-loss and SSH-disconnect rehearsal: PASS
Resuming round six on fix/deploy-688-round5 at base/head
768dbacbb6. Previous saved real staging and extra regression rehearsals exited 0 after reboot; exhaustive gate has no exit marker and is incomplete. Rechecking services and fixtures, then rerunning gates from the beginning under one build slot. The explicit job instruction to push only this branch and rehearse staging takes precedence over the general new-branch/no-deploy boilerplate. No production access or merge.Current resumption health check: calternal-next.service is active and its running digest is 60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb. /healthz returns {"revision":"8dc52337a566158e86b0234bcfb082331d76d1cd","status":"ok","version":"0.1.0"}. The private mode-0600 image record agrees and preserves rollback 26ec8a7decf4c161acbabf8c99ebb2ba868075dcd4697fe08df356295aab8108. Both forgejo-runner.service and forgejo-runner-web.service are active since 06:43:49 CET with NRestarts=0. There are no failed system units or leftover round6 disposable stores.
The saved post-reboot real staging rehearsals have both exit markers 0 and all 25 rows PASS. Their results were already posted at comments 69160 and 69169, and remain valid on the unchanged
768dbacbhead and the same boot. No staging rerun is necessary. The interrupted exhaustive gate has no completed result; a fresh full run is now active in one build slot, with output kept separately from the interrupted log. No product edits are needed from this resumption's diff review so far.Evidence audit found the complete pre-reboot log still available on unchanged product/test source, with explicit PASS positions through bootstrap-health-fails after 80/106. Those results were not lost. To follow the instruction to resume completed work, stopped only the current resumption's recorded timeout PID 534890, verified by /proc command and owner. Its exit 143 is retained and is not a passing gate.
The remaining gate uses an untracked resume harness. It reads the original matrix list from the committed test's AST, runs each baseline again, checks retained step counts, and calls the original inject function on every position without an explicit retained PASS. It keeps the original three-worker maximum and one build slot. Syntax, shellcheck, Quadlet, transport, ordinary transaction, bootstrap, relay, artifact and dry-run gates run fresh. Final evidence will state retained and fresh counts, include both original logs and resume source, and will not describe this as an uninterrupted full rerun. All 25 real staging rows already passed on this boot and unchanged head.
Round-six verification checkpoint on unchanged head
768dbacbb6: 2802 injection positions are now fully accounted PASS from retained explicit results plus fresh missing positions. Bootstrap health-failure and persistent-full-disk sweeps are complete, as are failed deploy and failed rollback ordinary and persistent-full-disk sweeps. The original matrix continues through stale-primary recovery and post-start unit-rename recovery. No failed test or new product defect has appeared. This is still a running gate, not a final gate pass. All 25 real staging rehearsal rows remain recorded PASS after this boot. No product file changes, production access or merge.Resuming deploy-fix-8 on fix/deploy-688-round5, base/head
768dbacbb6. Work only in /home/agent/wt/deploy-fix. The explicit task-specific instruction to push this branch overrides the older generic new-branch instruction. No production contact, merges, or production deployment. Post-reboot staging calternal-next and both CI runners active with zero restarts. Health reports expected8dc52337a5; real running image 60f680ae778350c332b127e477c3db3590927d4040007f0d9446c4d5d07c57eb. Both runners have active jobs; no failed system units. RAM available 58GiB; root free 124GiB; systemd-oomd active. Product/test source is unchanged from WIP. Retained explicit passing injection positions will be combined with fresh remaining positions; all other gates and real rehearsals will run on this boot, one build slot, maximum three workers. Evidence remains untracked.Verification checkpoint on unchanged head
768dbacbb6: 3498 injection positions are fully accounted PASS from retained explicit results plus fresh positions. Stale-primary rollback recovery now passes every ordinary before/after failure and persistent-full-disk before-command failure; persistent after-command sweep is running. Fresh syntax, shellcheck, Quadlet, transport, transaction and bootstrap checks passed. No new product defect or code change. This is still a running gate, not a final gate pass. Fresh real staging rehearsals follow in the same single-slot sequence.Regression gate completed on unchanged head
768dbacbb6, exit 0. Exhaustive failure injection: 4,354 cases = 3,314 retained explicit passes + 1,040 fresh passes; userdata unchanged. Fresh syntax, shellcheck, all other deploy probes, dry runs and real user-systemd checks pass. Fresh real staging rehearsal is running next in one build slot. Final completion comment will include verbatim gate output and all real rehearsal rows. No code changes, production access or merge.Completed deploy-fix-8 on
fix/deploy-688-round5, head768dbacbb6b25773af560b5e10f69a9fbd88f1da. The WIP commit already contained all four fixes; review and verification found no additional product changes necessary. No new or empty commits were made. Pushed only the requested branch. No production contact, merges, or issue closure.Finding → fix → test:
deploy.sh bootstrapreplaces manual stop-before-tag steps. Verified image, unit and pointer preparation, free-space admission, static recovery and independent runtime reserves finish before stop. Real unlabelled bootstrap, candidate failure, legacy rollback and full disk before/after admission passed. Both bootstrap full-disk cases observed zero available blocks and preserved the exact old MainPID.-; successful update records the old baseline. Runtime/inspection errors refuse before stop. Regression checks verify first update, failed first update, absent rollback refusal and preservation of older predecessors.Post-reboot health was verified before any rehearsal: both staging services (calternal-next and calternal-preview) active, zero restarts; next health and exact image/pointer identities matched expectations. Both CI runners active with zero restarts and running jobs. No failed system or calternal user units. CI slice MemoryMax=32 GiB and agent slice MemoryMax=24 GiB, with oomd enabled. All fresh heavy work used one build slot and no more than three workers. No build was needed.
Verification accounting: unchanged committed product/test source, with SHA-256 manifest attached. Explicit passing injection positions retained from the two interrupted runs were combined with freshly executed missing positions using the original injection function and matrix. Every baseline was rerun and boundary counts checked. This is a completed combined sweep, not an uninterrupted full rerun. Other gates and all 25 real rehearsal rows ran fresh on this boot. Evidence is untracked; no artifacts were committed.
Real staging rehearsal (fresh this boot):
Rehearsals restored original staging unit bytes, image pointers and mount identities. Full disk cases used bounded dedicated filesystems; shared filesystems were never filled. Disposable fixtures were cleaned.
Complete evidence, retained output, resume source, source hashes and per-run logs.
Final health output, verbatim:
Gate output, verbatim:
Gate exit: 0. Real bootstrap/staging rehearsal exit: 0. Additional real regression and SSH-disconnect rehearsal exit: 0. Remote branch and PR head both verified as
768dbacbb6b25773af560b5e10f69a9fbd88f1da.Closing: superseded by #733 (named in the PR body or comments). Branch kept.
Pull request closed