Skip to content

Proposal: unenforced effect declarations below their enforcement version should warn #80

@marcelofarias

Description

@marcelofarias

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:

  1. 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.
  2. Register VER001/VER002 in `error-codes.ts` and `explanations.ts`.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestproposalRFC or design proposal

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions