feat(1e): HC3 additive generic + op/assertion split (orchestrator owns the op)

- orchestrator: per mutating tier, run optional pre-op seed hook (ops.py pre_<op>) → perform the op
  ONCE (harness-owned) → run generic assertion (unless opted out) AND overlay assertion, both against
  the shared post-op deployment. Op results passed op→assertion via run-scoped CCCI_OP_STATE_FILE.
- opt-out: CCCI_SKIP_GENERIC / CCCI_SKIP_GENERIC_<OP> / recipe_meta.SKIP_GENERIC (declarative).
- generic.py: split do_* into op primitives (perform_upgrade/backup/restore) + assertions
  (assert_upgraded/backup_artifact/restore_healthy) reading op_state(); deployed_identity now returns
  {version,image,chaos} (chaos label ready for HC1).
- generic test_<op>.py + all 6 recipe overlays migrated to assertion-only; pre-op seeding moved to
  per-recipe ops.py (pre_upgrade/pre_backup/pre_restore). install overlays unchanged (no op).
- deploy-count stays 1 (op primitives never call deploy_app). lint PASS; 8 unit tests PASS on cc-ci.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-28 03:12:04 +01:00
parent 6a59343996
commit b7e6cbd7be
31 changed files with 623 additions and 412 deletions

View File

@ -1,9 +1,10 @@
"""Generic BACKUP tier (Phase 1d DG3) — recipe-agnostic, backup-capable recipes only.
"""Generic BACKUP tier (Phase 1d DG3 + Phase 1e HC3) — recipe-agnostic, assertion-only.
Runs `abra app backup create` against the shared live deployment and asserts a snapshot artifact is
produced (abra app backup snapshots is non-empty). Honest limit: the generic verifies the backup
MECHANISM, not app-specific data integrity — that's a recipe overlay (test_backup.py seeds a marker).
For recipes that declare no backup config the orchestrator skips this tier as N/A (not a failure)."""
The orchestrator ran `abra app backup create` ONCE against the shared live deployment and recorded
the produced snapshot id in the run-scoped op state. This tier ASSERTS a snapshot artifact was
produced — it does NOT perform the op. Honest limit: the generic verifies the backup MECHANISM, not
app-specific data integrity — that's a recipe overlay (test_backup.py). Runs by default ALONGSIDE any
overlay (additive). For recipes that declare no backup config the orchestrator skips this tier (N/A)."""
import os
import sys
@ -13,5 +14,4 @@ from harness import generic # noqa: E402
def test_backup_artifact(live_app, meta):
snaps = generic.do_backup(live_app)
assert snaps, "backup produced no snapshot artifact"
assert generic.assert_backup_artifact(live_app), "backup produced no snapshot artifact"

View File

@ -1,8 +1,9 @@
"""Generic RESTORE tier (Phase 1d DG3) — recipe-agnostic, backup-capable recipes only.
"""Generic RESTORE tier (Phase 1d DG3 + Phase 1e HC3) — recipe-agnostic, assertion-only.
Restores the latest snapshot (produced by the backup tier on the same shared deployment) and asserts
the restore completes and the app is healthy + serving afterwards. App-specific data-integrity
(marker survives) is a recipe overlay (test_restore.py); the generic verifies the restore mechanism."""
The orchestrator restored the latest snapshot ONCE (produced by the backup op on the same shared
deployment). This tier ASSERTS the restore completed and the app is healthy + serving afterwards — it
does NOT perform the op. App-specific data-integrity (marker survives) is a recipe overlay
(test_restore.py); the generic verifies the restore mechanism. Runs by default ALONGSIDE any overlay."""
import os
import sys
@ -12,4 +13,4 @@ from harness import generic # noqa: E402
def test_restore_healthy(live_app, meta):
generic.do_restore(live_app, meta)
generic.assert_restore_healthy(live_app, meta)

View File

@ -1,9 +1,10 @@
"""Generic UPGRADE tier (Phase 1d DG2) — recipe-agnostic.
"""Generic UPGRADE tier (Phase 1d DG2 + Phase 1e HC3) — recipe-agnostic, assertion-only.
The orchestrator deployed the PREVIOUS published version once; this tier upgrades it IN PLACE
(abra app upgrade) to the target (VERSION env, else newest published) on the same live deployment,
then asserts it reconverges and still serves. Data-continuity is a recipe overlay (test_upgrade.py),
not the generic — the generic verifies the upgrade mechanism + still-serving."""
The orchestrator deployed the base version once and performed the upgrade ONCE in place (Phase 1e
HC1: to the PR-head code under test via `abra app deploy --chaos`), recording the pre-upgrade
identity in the run-scoped op state. This tier ASSERTS the upgrade reconverged, still serves, and
actually MOVED the deployment (version/image/chaos label) — it does NOT perform the op. Runs by
default ALONGSIDE any recipe overlay (additive); skipped only via an explicit opt-out."""
import os
import sys
@ -13,5 +14,4 @@ from harness import generic # noqa: E402
def test_upgrade_reconverges(live_app, meta):
target = os.environ.get("VERSION") or None
generic.do_upgrade(live_app, target, meta)
generic.assert_upgraded(live_app, meta)