softwaremill / softwaremill/ox

Adopt capture checking: capture-aware fork/Flow signatures, tracked Ox capability

Open
#469 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Scala
Stars
522
Forks
35
Avg merge
45m
Merged PRs (30d)
13

Description

Motivation

In orca we split side-effect gating into capability tokens — a shared one that may legally cross fork boundaries (LLM-call gate, caps.SharedCapability) and exclusive ones that must not (workspace/git-index writers, caps.ExclusiveCapability) — and we want Scala's capture + separation checking to reject an exclusive capability captured into a fork closure at compile time.

That works today, but only through a wrapper, because of an empirically verified limitation: separation checking does not fire through combinators of a library that was not itself compiled under capture checking. Calling ox.fork or ox.flow.Flow.mapParUnordered from a CC-enabled file yields zero enforcement — the checker needs the combinator's own signature to declare what the closures may capture.

Our bridge is CheckedPar — a 1:1 delegate to Flow.mapParUnordered, compiled under captureChecking + separationChecking, whose capture-set parameter (thunks: Seq[() ->{C} T] with C^) makes the call site checkable. With it, a closure capturing an exclusive capability is a hard "Separation failure" compile error while shared capabilities pass — both directions pinned by a dotc-in-test negative-compile suite. The wrapper is explicitly a bridge designed for deletion once Ox carries the annotations natively (design record: ADR 0018 §6).

What Ox would need

  1. Capture-aware signatures on the closure-accepting concurrency APIfork/forkUser/etc., supervised/unsupervised bodies, and the Flow parallel combinators (mapPar, mapParUnordered, …), so separation checking evaluates each caller's closure captures at the call site. In our experiments, plain =>-style params on a CC-compiled combinator suffice for the hidden-set rules to engage; an explicit capture-set type parameter (as in CheckedPar) gives callers something to bind.
  2. Track the Ox capability itself — so a fork handle or Ox-scoped resource escaping its supervised scope becomes a compile error. Downstream this would also let us make "agent value outlives its owning scope" a compile error rather than a runtime latch.

Findings from our adoption that may save you time (all verified on stock Scala 3.8.4, no nightly, no flags)

  • caps.Capability is sealed; caps.SharedCapability is non-experimental (extendable with zero language imports), caps.ExclusiveCapability is @experimental (defining files need the CC import).
  • No consumer taint for definitions: a jar built from CC-importing files is consumable by plain non-CC consumers with zero flags. But a signature carrying a capture-set parameter (C^) is only callable from CC-enabled compilation units — an API-surface consequence worth designing around (it's why CheckedPar is deliberately not on orca's exported surface).
  • Enforcement locus is the caller's compilation unit: both the combinator and each calling file need the language imports for violations to be caught.
  • Tooling caveats: scalafmt (3.8.3) cannot tokenize the ^ syntax (per-file exclusion needed); tapir's derives schema derivation doesn't typecheck under CC (keep macro-deriving types out of CC-compiled files); munit's compileErrors/typeCheckErrors are blind to CC violations (CC runs post-typer) — negative tests need separate compilation.

Happy to share more details from the adoption or test fixtures if useful.

🤖 Generated with Claude Code

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the CheckedPar.scala bridge, CcNegativeCompileTest.scala, and ADR 0018 §6, then inspect Ox's fork, supervised, and Flow parallel-combinator APIs. Define capture-aware signatures and tracking for the Ox capability, with separate compilation tests showing exclusive captures and escaping scoped resources fail while shared capabilities pass.

Written by the indexing model from the issue text.

Assessment

Tech stack
scala
Domain
backend, backend-api-design
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.