The gap
Effect annotations have different enforcement version floors:
- `uses { }` + CAP001/CAP002: enforced from `?bs 0.2/0.3`
- `reads { }` / `writes { }` + DEP001/DEP002: enforced from `?bs 0.9`
- `throws { }` + THR001/THR002/THR003: enforced from `?bs 0.9`
Below their enforcement floor, these annotations are parsed and accepted silently. This creates a false assurance problem:
```botscript
?bs 0.8
fn loadUser(id: string)
reads { userDb }
throws { NetworkError }
-> Result<User, string> {
// ... calls other fns that read userDb or throw NetworkError
}
```
At `?bs 0.8`, reads {} and `throws {}` are pure documentation — DEP001/DEP002/THR001 do not fire. A reviewer reading this header would reasonably assume the compiler has verified the transitivity claim. It has not.
The risk materialises when a team is mid-upgrade (`?bs 0.8` → `?bs 0.9`) and writes `throws {}` annotations while still on 0.8. The annotations look correct but are untested. When they finally upgrade to 0.9, they may discover the entire call graph needs new declarations — a large, surprising diff.
Proposed fix: CAP004-style warning
Add a non-blocking warning (same pattern as CAP003) when an effect clause is present on a file pinned below the version that enforces it:
```
WARN reads { … } or writes { … } declared at ?bs 0.8 — enforcement (DEP001/DEP002) requires ?bs 0.9; this annotation is unenforced
WARN throws { … } declared at ?bs 0.8 — enforcement (THR001) requires ?bs 0.9; this annotation is unenforced
```
Codes: VER001 (reads/writes unenforced) and VER002 (throws unenforced), or a combined VER001 with a version gate arg.
Why a warning, not an error: banning the annotation entirely would break the valid pattern of "annotating first, then upgrading the pin." A warning preserves the annotation for documentation while making the lack of enforcement visible.
What changes:
- In `dep-check.ts` / `thr-check.ts`: detect when the fn has a `reads {}` / `writes {}` / `throws {}` clause but the resolved version is below 0.9. Emit a non-blocking warning.
- Register VER001/VER002 in `error-codes.ts` and `explanations.ts`.
- Surface warnings through `TransformResult.warnings` (same as CAP003).
Relationship to existing diagnostics
- CAP003 — warns when `uses {}` is on an `unsafe fn` (asserted, not proven). VER001/VER002 warn when an annotation exists below the version where it's enforced (declared, not proven).
- FMT001 — rejects non-canonical source. VER001/VER002 are semantic, not syntactic.
- The long-term fix is upgrading the pin. The warning is a nudge, not a block.
Open question
Should VER001/VER002 fire for every fn with an unenforced clause, or only when the clause is non-empty (e.g. `reads { userDb }` but not `reads {}`)? Empty clauses at old versions are more likely intentional forward-declarations for documentation; non-empty clauses are more likely to cause surprises.
The gap
Effect annotations have different enforcement version floors:
Below their enforcement floor, these annotations are parsed and accepted silently. This creates a false assurance problem:
```botscript
?bs 0.8
fn loadUser(id: string)
reads { userDb }
throws { NetworkError }
-> Result<User, string> {
// ... calls other fns that read userDb or throw NetworkError
}
```
At `?bs 0.8`,
reads {}and `throws {}` are pure documentation — DEP001/DEP002/THR001 do not fire. A reviewer reading this header would reasonably assume the compiler has verified the transitivity claim. It has not.The risk materialises when a team is mid-upgrade (`?bs 0.8` → `?bs 0.9`) and writes `throws {}` annotations while still on 0.8. The annotations look correct but are untested. When they finally upgrade to 0.9, they may discover the entire call graph needs new declarations — a large, surprising diff.
Proposed fix: CAP004-style warning
Add a non-blocking warning (same pattern as CAP003) when an effect clause is present on a file pinned below the version that enforces it:
```
WARN reads { … } or writes { … } declared at ?bs 0.8 — enforcement (DEP001/DEP002) requires ?bs 0.9; this annotation is unenforced
WARN throws { … } declared at ?bs 0.8 — enforcement (THR001) requires ?bs 0.9; this annotation is unenforced
```
Codes: VER001 (reads/writes unenforced) and VER002 (throws unenforced), or a combined VER001 with a version gate arg.
Why a warning, not an error: banning the annotation entirely would break the valid pattern of "annotating first, then upgrading the pin." A warning preserves the annotation for documentation while making the lack of enforcement visible.
What changes:
Relationship to existing diagnostics
Open question
Should VER001/VER002 fire for every fn with an unenforced clause, or only when the clause is non-empty (e.g. `reads { userDb }` but not `reads {}`)? Empty clauses at old versions are more likely intentional forward-declarations for documentation; non-empty clauses are more likely to cause surprises.