fix(delivery): reject incompatible release selection

This commit is contained in:
2026-10-21 11:50:40 +08:00
parent cf43ae6c75
commit d66d42d021
3 changed files with 182 additions and 1 deletions
+10
View File
@@ -104,6 +104,16 @@ factory socket, and empty task/identity/capture resources. Root journals and
batches are retained. Interruption between pointer writes requires inspection.
`check-active` checks code/configuration selection and the measurement snapshot.
When replacing an existing selection, `activate` fully verifies both releases
and requires identical nonempty Backend migration inventories, runtime images,
platform, toolchains and dependency/contract lock identities. A compatible
software rollback uses the same explicit command with its previously installed
release and configuration batch. An incompatible selection is refused before
either pointer is written. First installation needs no prior selection.
Migration-changing selection is unavailable through this command; it never
downgrades a database or runs Alembic. Normal startup and full readiness remain
separate checks, and activation success does not establish recovery success.
## PostgreSQL and RabbitMQ
### Prepare dependencies
+27 -1
View File
@@ -469,6 +469,25 @@ def require_stopped() -> None:
raise DeliveryError("delegated task groups remain; cleanup proof required")
def _compatible_selection(current: dict, target: dict) -> None:
"""Select software only across the same declared schema and runtime ABI."""
if any(current[key] != target[key] for key in ("platform", "toolchains", "images", "locks")):
raise DeliveryError(
"release selection requires identical runtime/toolchain/lock identities"
)
def migrations(value: dict) -> dict:
return {
item["path"]: item
for item in value["files"]
if item["path"].startswith("backend/migrations/")
}
previous = migrations(current)
if not previous or previous != migrations(target):
raise DeliveryError("release selection requires identical nonempty Backend migrations")
@manifest.verified_operation
def activate(release_id: str, expected: str, batch: str) -> dict:
"""Only files + separately installed exact configuration may be selected.
@@ -517,7 +536,14 @@ def activate(release_id: str, expected: str, batch: str) -> dict:
if ACTIVE.exists() or ACTIVE.is_symlink() or CURRENT.exists() or CURRENT.is_symlink():
# Retiring a byte-bound old batch is allowed after a kernel change.
# It is never accepted for execution under that changed environment.
selection(check_configuration=False)
previous = selection(check_configuration=False)
current_data = manifest.verify(
RELEASES / previous["release_id"], previous["manifest_sha256"]
)
target_data = manifest.verify(release, expected)
_compatible_selection(current_data, target_data)
if selection(check_configuration=False) != previous:
raise DeliveryError("selected binding changed before activation")
active = {
"schema_version": 1,
"release_id": release_id,