fix: declare the capabilities the modules actually use - #3
Merged
Merged
Conversation
march main enforces the capability ceiling on IO.Spawn, IO.Clock and IO.Process, and nine modules here call builtins requiring them without a `needs` declaration. Each declaration below is exactly what the compiler reported, nothing wider: CronScheduler IO.Spawn DeadLetter IO.Clock Config IO.Clock Pruner IO.Spawn Queue IO.Clock Worker IO.Spawn, IO.Clock Dashboard IO.Spawn Node IO.Spawn, IO.Process API IO.Clock This breaks every downstream consumer that resolves conduit from main, which is how forgepm depends on it: forgepm's CI typechecks against march main and fails on these eleven errors before it reaches its own code. Verified by typechecking forgepm against march main with this branch and bastion's Bytes fix in place — clean, no errors.
The first version of this branch left this one out, on the strength of a local check that reported it as a warning. Conduit's own CI reports it as an error and fails the build — the difference is toolchain: CI resolves march-version `main` (cbb8346e at time of writing), which is ahead of the build I checked against. The module prints progress while copying migrations, so the declaration is straightforwardly correct regardless.
`forge build` passes with the library declarations, but `forge test` compiles the test module too, and ConduitTest calls random_bytes in 130 places without declaring the capability. Verified: 242 tests, 0 failures.
March runs two separate capability checks. The typecheck-time one ("function
body calls a builtin that requires Cap(X)") is what the earlier commits on
this branch addressed. There is a second, stricter check at codegen —
"CAPABILITY CEILING: every module's emitted code must stay within its own
`needs`" — which reported 17 further violations, mostly IO.Mut.
The local toolchain I had been verifying against does not run that check, so
each CI round surfaced one more layer. This declares all 17 at once:
IO.Mut CronRegistry, EventStore, Node, Queue, QueueControl, Shutdown,
Storage.Postgres, WorkflowContext, WorkflowRegistry, ConduitTest
IO.Clock RateLimiter, WorkflowContext, WorkflowRunner, ConduitTest,
ConduitPostgresLiveTest
IO.Process ConduitTest, ConduitPostgresLiveTest
Every line is exactly what the check named; none is wider. 242 tests, 0
failures.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Unblocks every consumer that resolves conduit from
mainagainst current marchmain.Why
march
mainenforces capability declarations, and conduit declares almost none. forgepm depends on conduit as a git dep onmainand its CI typechecks against marchmain, so it currently fails on conduit's errors before reaching any of its own code.March runs two capability checks, not one
This took several CI rounds to establish, and it is the useful finding here:
function body calls a builtin that requires Cap(X) but <module> does not declare needs X. Reported per call site.CAPABILITY CEILING: every module's emitted code must stay within its own needs. Stricter, reported per module, and it catches capabilities that reach a module through code it emits rather than through a literal builtin call in its own body. This is where all theIO.Mutviolations came from.A module can satisfy (1) and still fail (2).
forge buildruns the first;forge testruns both.What changed
29
needslines across 15 modules and the two test modules. Every line is exactly what the compiler named — none is wider.IO.SpawnIO.ClockIO.ProcessIO.ConsoleIO.MutIO.Random,IO.Clock,IO.Mut,IO.Process), ConduitPostgresLiveTest (IO.Clock,IO.Process)No behaviour change — these declare authority the code already exercises.
Verification
CI green on macos-14 and ubuntu-24.04. Locally: 242 tests, 0 failures.
Also verified downstream: typechecking forgepm against march
mainwith this branch plus bastion#7 is clean, zero errors. Without them, forgepm's CI reports these capability errors plus 5 bastionBytestype errors from march#247.One thing deliberately left out
lib/conduit/rate_limiter.march:50has an ambiguousCustomconstructor (Conduit.CustomvsCompress.Gzip.Custom). It does not reproduce on CI's toolchain, only on an older local build, and there is already a fix for it authored on another branch. Left alone rather than duplicated here.