Sprint 20: Driver Swap
Prerequisites
Sprints 18–19 — hello-world works, CLI complete.
Goals
Wire afs-ld into the armfortas driver. Initially gated behind AFS_LD=1. After the Sprint 27 parity gate, flip the default. Keep a fallback to system ld for at least one sprint after default-on.
Deliverables
1. Driver change site
armfortas/src/driver/mod.rs:
Two call sites ship today:
- Single-file link path at lines 497–530.
- Multi-file link path at lines 533–565.
Both build a Command::new("ld"). Refactor to:
fn linker_command() -> (Command, &'static str) {
match env::var("AFS_LD").as_deref() {
Ok("1") | Ok("true") => (Command::new(find_afs_ld()), "afs-ld"),
_ => (Command::new("ld"), "system ld"),
}
}
find_afs_ld():
AFS_LD_PATHenv var (full path to the binary).<workspace>/target/debug/afs-ld.<workspace>/target/release/afs-ld.PATHlookup.
Failure produces a clear diagnostic pointing to the env var and build commands.
2. Testing harness update
armfortas/src/testing.rs:871-908 (used by integration tests) — same refactor. Integration tests respect AFS_LD=1.
3. Flag pass-through parity
The driver today builds a fixed command. After this sprint it still builds the same command — afs-ld accepts the same flags. Differential in practice:
# System ld
ld hello.o libarmfortas_rt.a -lSystem -no_uuid -syslibroot <SDK> -e _main -o hello
# afs-ld (same args)
afs-ld hello.o libarmfortas_rt.a -lSystem -no_uuid -syslibroot <SDK> -e _main -o hello
4. Fallback semantics
If afs-ld errors, produce a driver-level diagnostic that cites afs-ld's exit status and stderr, plus a hint to retry with AFS_LD=0. Do not automatically retry with system ld — silently falling back masks real bugs.
5. Test coverage on both paths
cargo test --workspace runs all integration tests twice: once with AFS_LD=0 (baseline), once with AFS_LD=1. Divergence is a test failure. This is the CI gate for afs-ld adoption.
6. Preserving -no_uuid determinism
Driver passes -no_uuid today. Verify afs-ld honors it byte-identically: same inputs under same seed produce the same output (no process-id, no timestamp, no random padding).
7. Docs
Update armfortas/CLAUDE.md with a note about AFS_LD=1 and how to enable/disable. armfortas/README.md if/when it mentions linking.
Testing Strategy
tests/linker_swap.rs: runs hello-world both ways, asserts the binaries differ only in tolerated regions.- Integration suite under
AFS_LD=1: every existing integration test must pass. This is the gate. - Failure-path test: a deliberately-broken link (missing symbol); both paths produce an error, not a segfault.
Definition of Done
AFS_LD=1 cargo test --workspacepasses every test green.- Driver refactor lands on a branch that can be rolled back cleanly by flipping the env-var default.
- Diagnostic quality on afs-ld failures matches or exceeds system ld's.
- No silent fallback — afs-ld failures surface loudly.
View source
| 1 | # Sprint 20: Driver Swap |
| 2 | |
| 3 | ## Prerequisites |
| 4 | Sprints 18–19 — hello-world works, CLI complete. |
| 5 | |
| 6 | ## Goals |
| 7 | Wire afs-ld into the armfortas driver. Initially gated behind `AFS_LD=1`. After the Sprint 27 parity gate, flip the default. Keep a fallback to system `ld` for at least one sprint after default-on. |
| 8 | |
| 9 | ## Deliverables |
| 10 | |
| 11 | ### 1. Driver change site |
| 12 | `armfortas/src/driver/mod.rs`: |
| 13 | |
| 14 | Two call sites ship today: |
| 15 | - Single-file link path at lines 497–530. |
| 16 | - Multi-file link path at lines 533–565. |
| 17 | |
| 18 | Both build a `Command::new("ld")`. Refactor to: |
| 19 | |
| 20 | ```rust |
| 21 | fn linker_command() -> (Command, &'static str) { |
| 22 | match env::var("AFS_LD").as_deref() { |
| 23 | Ok("1") | Ok("true") => (Command::new(find_afs_ld()), "afs-ld"), |
| 24 | _ => (Command::new("ld"), "system ld"), |
| 25 | } |
| 26 | } |
| 27 | ``` |
| 28 | |
| 29 | `find_afs_ld()`: |
| 30 | 1. `AFS_LD_PATH` env var (full path to the binary). |
| 31 | 2. `<workspace>/target/debug/afs-ld`. |
| 32 | 3. `<workspace>/target/release/afs-ld`. |
| 33 | 4. `PATH` lookup. |
| 34 | |
| 35 | Failure produces a clear diagnostic pointing to the env var and build commands. |
| 36 | |
| 37 | ### 2. Testing harness update |
| 38 | `armfortas/src/testing.rs:871-908` (used by integration tests) — same refactor. Integration tests respect `AFS_LD=1`. |
| 39 | |
| 40 | ### 3. Flag pass-through parity |
| 41 | The driver today builds a fixed command. After this sprint it still builds the same command — afs-ld accepts the same flags. Differential in practice: |
| 42 | |
| 43 | ``` |
| 44 | # System ld |
| 45 | ld hello.o libarmfortas_rt.a -lSystem -no_uuid -syslibroot <SDK> -e _main -o hello |
| 46 | |
| 47 | # afs-ld (same args) |
| 48 | afs-ld hello.o libarmfortas_rt.a -lSystem -no_uuid -syslibroot <SDK> -e _main -o hello |
| 49 | ``` |
| 50 | |
| 51 | ### 4. Fallback semantics |
| 52 | If afs-ld errors, produce a driver-level diagnostic that cites afs-ld's exit status and stderr, plus a hint to retry with `AFS_LD=0`. Do **not** automatically retry with system `ld` — silently falling back masks real bugs. |
| 53 | |
| 54 | ### 5. Test coverage on both paths |
| 55 | `cargo test --workspace` runs all integration tests twice: once with `AFS_LD=0` (baseline), once with `AFS_LD=1`. Divergence is a test failure. This is the CI gate for afs-ld adoption. |
| 56 | |
| 57 | ### 6. Preserving `-no_uuid` determinism |
| 58 | Driver passes `-no_uuid` today. Verify afs-ld honors it byte-identically: same inputs under same seed produce the same output (no process-id, no timestamp, no random padding). |
| 59 | |
| 60 | ### 7. Docs |
| 61 | Update `armfortas/CLAUDE.md` with a note about `AFS_LD=1` and how to enable/disable. `armfortas/README.md` if/when it mentions linking. |
| 62 | |
| 63 | ## Testing Strategy |
| 64 | - `tests/linker_swap.rs`: runs hello-world both ways, asserts the binaries differ only in tolerated regions. |
| 65 | - Integration suite under `AFS_LD=1`: every existing integration test must pass. This is the gate. |
| 66 | - Failure-path test: a deliberately-broken link (missing symbol); both paths produce an error, not a segfault. |
| 67 | |
| 68 | ## Definition of Done |
| 69 | - `AFS_LD=1 cargo test --workspace` passes every test green. |
| 70 | - Driver refactor lands on a branch that can be rolled back cleanly by flipping the env-var default. |
| 71 | - Diagnostic quality on afs-ld failures matches or exceeds system ld's. |
| 72 | - No silent fallback — afs-ld failures surface loudly. |