feat(P1): sync_to_nova.sh — manual-only 2nd-release pipeline into ~/nova (REQ-229)
Replaces scripts/sync_to_gl.sh (kitchen-sink mirror sync into ~/gl/acdl) with scripts/sync_to_nova.sh — a manual-only, consumer-subset, domain-committed 2nd-release pipeline into ~/nova (GitLab jonathanchery/nova, separate repo + history, consumer/platform-team audience). - Manual-only gate: refuses without --release / RELEASE_CONFIRMED=1 (exit 2). Never triggerable by CI. - Consumer subset: excludes .ciagent/, .gitea/, .env*, terraform/, demo/, runtime metrics artifacts, and 18 internal-only scripts (EXCLUDE_SCRIPTS). Keeps consumer runbooks + metrics export views (README, powerbi, TRUST_SNAPSHOT). Protects ~/nova/.git via rsync --filter=P .git. - Domain-based commits: 13 fixed-order domains (config, core, adapters, modules, contracts, schemas, pipelines, mcp, skills, scripts, tests, docs, workflows). Each changed domain gets its own conventional commit supplied positionally via repeated -m flags. No kitchen-sink commit. - Conventional-commit validation: regex-enforced (feat|fix|docs|chore|...); bypass via --no-verify-format. - Modes: --list-domains, --dry-run, --no-push, -v, -h. - Tests: TestSyncToNovaScript (8 tests) covers gate, domain order, exclude list, consumer-script inclusion, .git protection filter, conventional regex. Decisions: D-143 (target ~/nova), D-144 (conventional commits per domain, not ---ci--- audit blocks), D-145 (manual-only trigger), D-146 (13 fixed domains, positional-over-changed mapping), D-147 (coreci/Atelier review gate deferred). ---ci--- project: acdl phase: 1 milestone: v1.19 status: execute requirements: covered: [REQ-229] partial: [] ---/ci---
This commit is contained in:
@@ -66,17 +66,71 @@ class TestPushConsumerImage:
|
||||
assert "ecr" in cmd
|
||||
|
||||
|
||||
class TestSyncToGlScript:
|
||||
"""scripts/sync_to_gl.sh — test structure (set flags, usage)."""
|
||||
|
||||
def test_has_set_flags(self):
|
||||
"""v1.14 (P16): sync_to_gl.sh should have set -euo pipefail."""
|
||||
script = (ROOT / "scripts" / "sync_to_gl.sh").read_text()
|
||||
# P16 will add this; for now just verify the script exists
|
||||
assert "cp" in script or "rsync" in script
|
||||
class TestSyncToNovaScript:
|
||||
"""scripts/sync_to_nova.sh — manual-only 2nd release into ~/nova (REQ-229)."""
|
||||
|
||||
def test_script_exists(self):
|
||||
assert (ROOT / "scripts" / "sync_to_gl.sh").is_file()
|
||||
assert (ROOT / "scripts" / "sync_to_nova.sh").is_file()
|
||||
|
||||
def test_has_set_flags(self):
|
||||
script = (ROOT / "scripts" / "sync_to_nova.sh").read_text()
|
||||
assert "set -euo pipefail" in script
|
||||
|
||||
def test_manual_gate_refuses_without_release(self):
|
||||
"""Without --release the script must exit non-zero and never rsync."""
|
||||
result = subprocess.run(
|
||||
["bash", str(ROOT / "scripts" / "sync_to_nova.sh")],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 2
|
||||
assert "MANUAL-ONLY" in result.stderr or "manual" in result.stderr
|
||||
|
||||
def test_list_domains_prints_ordered_domains(self):
|
||||
"""--list-domains prints the 13 domains in commit order."""
|
||||
result = subprocess.run(
|
||||
["bash", str(ROOT / "scripts" / "sync_to_nova.sh"), "--list-domains"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0
|
||||
lines = [l for l in result.stdout.splitlines() if l and not l.startswith("DOMAIN")]
|
||||
names = [l.split()[0] for l in lines]
|
||||
# The 13 consumer domains, in commit order.
|
||||
assert names == [
|
||||
"config", "core", "adapters", "modules", "contracts",
|
||||
"schemas", "pipelines", "mcp", "skills", "scripts",
|
||||
"tests", "docs", "workflows",
|
||||
]
|
||||
|
||||
def test_internal_scripts_are_excluded(self):
|
||||
"""The EXCLUDE_SCRIPTS list must include the internal-only scripts."""
|
||||
script = (ROOT / "scripts" / "sync_to_nova.sh").read_text()
|
||||
# Isolate the EXCLUDE_SCRIPTS=( ... ) block.
|
||||
block = script.split("EXCLUDE_SCRIPTS=(")[1].split(")")[0]
|
||||
for internal in ("sync_to_gl.sh", "sync_to_nova.sh", "ship_phase.sh",
|
||||
"update_atelier_vendor.sh", "rotate_spike_key.sh",
|
||||
"post_stage_comment.sh", "untag_acdl_keys.py"):
|
||||
assert internal in block, f"{internal} missing from EXCLUDE_SCRIPTS"
|
||||
|
||||
def test_consumer_scripts_not_excluded(self):
|
||||
"""Consumer-facing runbooks must NOT be in the exclude list."""
|
||||
script = (ROOT / "scripts" / "sync_to_nova.sh").read_text()
|
||||
for consumer in ("run_ci.sh", "run_platform.sh", "run_regression.sh"):
|
||||
# They appear in scripts/ but must not be in EXCLUDE_SCRIPTS.
|
||||
assert f"\"{consumer}\"" not in script.split("EXCLUDE_SCRIPTS=(")[1].split(")")[0], \
|
||||
f"{consumer} should NOT be excluded (it's a consumer runbook)"
|
||||
|
||||
def test_git_filter_uses_protect_pattern(self):
|
||||
"""rsync must protect the destination's .git history (filter=P)."""
|
||||
script = (ROOT / "scripts" / "sync_to_nova.sh").read_text()
|
||||
assert "--filter=P .git" in script
|
||||
|
||||
def test_conventional_commit_regex_present(self):
|
||||
"""The script validates conventional commit format."""
|
||||
script = (ROOT / "scripts" / "sync_to_nova.sh").read_text()
|
||||
assert "CONV_RE" in script
|
||||
assert "feat|fix|docs|chore|refactor|perf|test|build|ci|style|revert" in script
|
||||
|
||||
|
||||
class TestPostStageComment:
|
||||
|
||||
Reference in New Issue
Block a user