feat(binary): check zuur/binary package.lua version against constraints

This commit is contained in:
2026-08-08 17:38:21 -04:00
parent c876821cda
commit b367f21d29
3 changed files with 469 additions and 0 deletions
+52
View File
@@ -183,6 +183,58 @@ _Auto-scaffolded by /start-work. Append new entries below - never overwrite._
---
## Task 10 — `tofu.binary` (zuur/binary package.lua version checking)
### Architecture
- Module `tofu.binary` depends on: `tofu.types` (BinaryCheckResult, DepConstraint), `tofu.http` (get, HttpException), `tofu.config` (Config, binaryManifestUrl), `tofu.vercmp` (satisfies), `tofu.log` (logDetail, logInfo).
- `checkBinaryVersion(name, constraint, cfg)` fetches the package.lua manifest from zuur/binary via HTTP, parses the version, and checks satisfaction.
### Key logic — 404 vs other errors
- HTTP 404 → binary doesn't exist (recipe-only). Return `BinaryCheckResult(false, "", false)`.
- Any other HttpException (500, timeout, connection failure) → **rethrow** — network problems must propagate, not be silently swallowed.
- Detection: `e.msg.canFind("404")` — the HttpException format is `"HTTP %d fetching <url>"`.
### Version extraction from Lua package.lua
- Light parse: scan for `version = "..."` in the manifest body.
- Word-boundary check on "version" keyword (preceding char must be whitespace/`{`/`,`/`;`, following char must be whitespace/`=`).
- If version field not found or malformed → `BinaryException`.
- This avoids a full Lua parser — package.lua manifests are flat key-value tables.
- Empty version string (e.g. `version = ""`) also throws BinaryException.
### Memoization
- Module-level `BinaryCheckResult[string] _versionCache` — keyed by package name.
- On cache hit: recompute satisfaction against caller's constraint (same version, different constraint possible).
- On 404: cache the "not found" result so subsequent calls don't re-fetch.
- Lifetime: one command run. Tests use distinct package names to avoid cross-state contamination.
- The cache stores `exists` and `ver`; `satisfies` is always recomputed per-constraint.
### `version` keyword trap (again)
- Local variable `version` conflicts with D keyword. Used `ver` instead. Same pattern as types.d where struct field is `ver`.
### Test infrastructure
- Reused local one-shot TCP server pattern from http.d:
- `bindAndSpawn(response)` — bind ephemeral port, spawn thread, return URL.
- `oneShotResponder(listener, response)` — accept one connection, send canned HTTP response, exit.
- `httpResponse(code, reason, body)` — build minimal HTTP/1.1 response with Content-Length.
- `testConfig(baseUrl)` — create a Config pointing zuurUrl at the test server.
### Test cases (6/6 pass)
1. Binary exists, version 2.1.0, constraint ge 2.0 → satisfies:true ✓
2. Binary exists, version 1.9, constraint ge 2.0 → satisfies:false ✓
3. 404 → exists:false, ver:"", satisfies:false ✓
4. 500 → HttpException rethrown (not swallowed) ✓
5. Manifest without version field → BinaryException ✓
6. Version 2.1.0 with unconstrained dep (op none) → satisfies:true ✓
### `@safe` annotation consistency
- `checkBinaryVersion` and all helpers marked `@safe`. The function calls `http.get()` (which is `@safe`), `satisfies` (`@safe`), and log functions (`@safe`). No `@trusted` blocks needed in production code.
- Test helpers (`oneShotResponder`, `bindAndSpawn`, `httpResponse`) are `@trusted` since they use `std.socket`.
### Note: pre-existing fetch.d compile issue
- `fetch.d` (from parallel task 7) has a missing `std.algorithm.searching : canFind` import that blocks `dub test`. `dub build` passes (fetch.d is excluded via `.skip` extension renames by the parallel agent). Binary.d tests verified via standalone `dmd -I... -i -main -unittest` compilation — all 7 modules pass.
---
## Task 7 — `tofu.index` (ZUUR index fetch & sandboxed Lua parse)
### Security: whitelist sandbox (NOT blacklist)