Migration Contract Tests¶
Overview¶
crates/amplihack-remote/tests/migration_contract_test.rs contains a suite of
permanent contract tests that guard the de-Pythonification migration (Epic #511,
Issue #536). These tests do not exercise runtime behaviour — they assert
structural properties of the repository that must remain true forever after the
migration is complete.
Each test encodes a "never regress" constraint. CI failure means a constraint has been violated and the offending commit must be reverted or corrected before merging.
Contract tests¶
github_hooks_scope_creep_is_absent¶
Constraint: The .github/hooks/ directory must not exist in the repository.
During the migration work, .github/hooks/ files were accidentally staged
alongside unrelated changes on feature branches. Issue #536 identified this
as a recurring risk and mandated a permanent enforcement mechanism.
This test resolves to the repository root at compile time (via
CARGO_MANIFEST_DIR) and asserts that the path .github/hooks does not exist
on the filesystem:
#[test]
fn github_hooks_scope_creep_is_absent() {
let hooks_dir = repo_root().join(".github/hooks");
assert!(
!hooks_dir.exists(),
"issue #536 forbids .github/hooks scope creep; remove {} before committing",
hooks_dir.display()
);
}
Why .github/hooks is forbidden
Git hooks belong in .git/hooks/ (local, never committed) or in a
project-managed directory like amplifier-bundle/modules/hook-*/ (versioned,
tested Python/Rust packages). A .github/hooks/ directory sits in neither
place: it is not automatically installed by Git and is not managed by the
amplihack module system. Committing files there creates confusion about which
hook system is authoritative and may interfere with CI runners that scan
.github/ for workflow definitions.
Remediation
If this test fails in CI, remove the directory before re-pushing:
git rm -r .github/hooks/
git commit -m "fix: remove .github/hooks scope creep (issue #536)"
git push
Note: .github/workflows/ is explicitly permitted and unaffected by this
constraint.
python_remote_tree_is_deleted_after_native_port¶
Constraint: No .py files may exist under
amplifier-bundle/tools/amplihack/remote/ after the Rust port.
The 25 Python source files that previously implemented amplihack remote have
been replaced by the amplihack-remote Rust crate. This test ensures the
Python tree is never re-introduced:
#[test]
fn python_remote_tree_is_deleted_after_native_port() {
let remote_dir = repo_root().join("amplifier-bundle/tools/amplihack/remote");
let mut python_files = Vec::new();
collect_python_files(&remote_dir, &mut python_files);
assert!(
python_files.is_empty(),
"issue #536 requires deleting every Python file under {}; still found: {:#?}",
remote_dir.display(),
python_files
);
}
remote_rust_modules_stay_under_500_lines¶
Constraint: Every .rs source file directly under crates/amplihack-remote/src/
must stay at or below 500 lines.
Issue #536 requires module size ≤ 500 lines as a readability and testability
gate. This test performs a shallow read_dir of src/ (not recursive)
and fails if any top-level .rs file exceeds the limit, reporting the
offending paths and their line counts. Sub-directories within src/ are not
checked by this test.
detached_sessions_default_to_32gb_node_heap_contract¶
Constraint: SessionManager::DEFAULT_MEMORY_MB must equal 32768.
Remote sessions launched by amplihack remote start inherit the
NODE_OPTIONS=--max-old-space-size=32768 preference. This test pins the
default so a refactor cannot silently lower the heap budget:
assert_eq!(
SessionManager::DEFAULT_MEMORY_MB,
32_768,
"remote start must persist memory_mb=32768 to match NODE_OPTIONS=--max-old-space-size=32768"
);
// Also verifies the default propagates when creating a session without an explicit override
let state_file = tempfile::tempdir()
.expect("temp dir should be created")
.path()
.join("remote-state.json");
let mut manager = SessionManager::new(Some(state_file)).expect("session manager should load");
let session = manager
.create_session("vm-a", "implement issue #536", Some("auto"), Some(10), None)
.expect("valid session should be created");
assert_eq!(
session.memory_mb, 32_768,
"sessions created without an override should persist the 32GB heap contract"
);
vm_size_tiers_match_documented_capacity_and_azure_skus¶
Constraint: VMSize capacity values and Azure SKU strings must match the
values documented in the architecture spec.
This test locks the mapping between the four VM tiers (S, M, L, XL),
their session-capacity multipliers, and the underlying Azure VM sizes.
Changing a SKU string requires updating both the code and this test.
assert_eq!(VMSize::S.capacity(), 1);
assert_eq!(VMSize::S.azure_size(), "Standard_D8s_v3");
assert_eq!(VMSize::M.capacity(), 2);
assert_eq!(VMSize::M.azure_size(), "Standard_E8s_v5");
assert_eq!(VMSize::L.capacity(), 4);
assert_eq!(VMSize::L.azure_size(), "Standard_E16s_v5");
assert_eq!(VMSize::XL.capacity(), 8);
assert_eq!(VMSize::XL.azure_size(), "Standard_E32s_v5");
Running the contract tests¶
# Run only the migration contract suite
cargo test -p amplihack-remote --test migration_contract_test --locked
# Run with verbose output to see which constraints are being checked
cargo test -p amplihack-remote --test migration_contract_test --locked -- --nocapture
All five tests run in under one second because they only inspect the filesystem and compile-time constants — no network calls, no VM allocation.
Adding a new contract test¶
- Add a
#[test]function tomigration_contract_test.rs. - Reference the GitHub issue that mandates the constraint in the
assert!message so future contributors can trace the reasoning. - Keep the test free of I/O side-effects: read files, check paths, inspect constants — never write, never spawn processes.
- Run
cargo test -p amplihack-remote --test migration_contract_test --lockedlocally before opening a PR.