Declare the dependencies Imp uses directly - #251
Merged
Merged
Conversation
Imp named modules from finch, mint, decimal, llm_db, plug and plug_crypto without declaring them. Declare finch, mint and decimal (optional); match the ReqLLM model by its fields instead of LLMDB's struct; declare plug and plug_cowboy runtime: false (ExMCP requires both in every environment, so Mix refuses :only); compare the demo token with :crypto.hash_equals/2; declare thousand_island for the test helper that reads Bandit's port. Remove jsv, which nothing uses. mix imp.deps.check, run by quality.check, fails when Imp names a module from an undeclared application.
… to its rule The check read only imports and atoms, so it let a shipped file name a runtime: false dependency, missed modules named as data or through macros, counted any atom that matched an Erlang module, passed silently in dev and raised on a deterministic build. It now walks the debug info's abstract code and Mix's compile references, counts a bare atom only where it is used as a module, excludes runtime: false dependencies from shipped files except ex_mcp and erlexec, runs in :test, fails on a name no application defines, and holds a module with no recorded source to the shipped rule. A probe test covers each reference shape. plug takes ExMCP's requirement, ~> 1.16. The demo plug refuses a wrong token of the right length, under test.
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.
Imp named modules from applications it reached only through other dependencies. This declares each one, removes a declared dependency nothing uses, and adds a check to
mix quality.checkso the gap fails CI next time.What changes
~> 0.21(Req's own floor).Imp.Clients.ReqLLMmatchesFinch.TransportErrorandFinch.Errorto tell a request that was never sent.~> 1.11, the only declaration here that moves an existing lock (an application that locked mint below 1.11.0 moves to it; the CHANGELOG says so), with the same line and comment as the 0.6.0 release branch (Release 0.6.0 (draft: version pending owner) #246).Imp.Clients.ReqLLMandImp.MCP.CallFailurematch Mint's error structs. It is here because the new check fails on main without it. The release branch's merge of main will conflict on the neighbouring lines ofdeps/0(this PR also removesjsvnext to where Release 0.6.0 (draft: version pending owner) #246 adds mint) and inCHANGELOG.md; both sides carry the same mint text.~> 2.0 or ~> 3.0,optional: true.Imp.Corereads a reported cost given as aDecimaland never creates one.mix hex.buildlists it as(optional), and the app file puts it inoptional_applications.Imp.Optimizer.GEPA.ConfidenceAdapternow matches%{provider: :openai}onReqLLM.model/1's result, not%LLMDB.Model{}.ReqLLM.model/1is specced to return{:ok, LLMDB.Model.t()}, so the struct match did no work. A new test covers the non-OpenAI clause. It passes against both the old code and the new, and fails when that clause is broken.~> 1.16and plug_cowboy~> 2.7, bothruntime: false: ExMCP's own requirements. The intent wasonly: [:dev, :test], but ExMCP requires both in every environment and Mix refuses the restriction ("Remove the :only restriction from your dep"), so declaring them adds nothing to a consumer's resolution. Imp starts neither, and no Imp code that ships uses them; the unpacked Hex package'slib/names no Plug module (Req lists plug as an optional application, so an application may still start it). The comment beside them says they become dev/test dependencies once ExMCP drops them.package_contract_testnow also asserts the two demo plug files stay out of the package.:crypto.hash_equals/2instead ofPlug.Crypto.secure_compare/2. Its guard already requires equal byte sizes, whichhash_equalsneeds.test/acp_demo_mcp_http_plug_test.exsrefuses a wrong token of the same length (and one of another length); replacing the comparison with one that accepts any non-empty token fails it.~> 1.5,only: :test.test/support/local_http.excallsThousandIsland.listener_info/1to read the Bandit server's port. The new check found this one.lib/,bench/ortest/names it: grep finds nothing, and neither the compile manifest nor the new check shows a reference. Its last user was the naming laboratory, pruned in 912f14d. No decision or doc records a reason to keep it. It stays inmix.lockbecause ReqLLM requires it.The check
mix imp.deps.check(a source-checkout task, not shipped) runs inquality.check, and its preferred environment is:test, where every declared dependency is built. For each compiled source file it collects::beam_libplus the backend'sdebug_info/4, as Erlang abstract code): remote calls and captures, struct names in patterns and constructions,@behaviour, everyElixir.-prefixed alias anywhere in the code (attribute values read in a function, keyword and map values, lists), and the module argument ofapply,is_structandCode.ensure_loaded?/ensure_compiled;import NimbleParsecand other macro-only uses appear. That API is not public; a change to it fails the task at compile time or in a match, not silently.An atom without the
Elixir.prefix counts only in those module positions, so%{mode: :jose}is data. An Erlang module held only as data, and any module name built at runtime (String.to_existing_atom/1), are not seen; the moduledoc says so. Typespecs are not read.Rules: a shipped file may name Imp, OTP/Elixir, or a dependency declared for every environment that Imp starts.
runtime: falsedependencies are excluded exceptex_mcpanderlexec, which the task names with their reason (started on the first protocol connection; bundled in:loadmode per docs/production.md). Other compiled files may also name dev/test andruntime: falsedependencies. A name no built application defines fails the check, except a name in Imp's own namespace, which is a registered process name (Imp.TaskSupervisor). A module with no recorded source is found through the manifest's module list; failing that, it is held to the shipped rule.test/imp_deps_check_test.exscompiles a probe module for each reference shape and checks it as a shipped file. The shapes:runtime: falsedependency, struct construction, struct pattern, struct named by an Erlang atom, attribute list, keyword value, map value, behaviour, Erlang remote call, capture,apply,is_struct,ensure_loaded?, unresolved name, and compile-time import through compiler references (the debug info alone passes it). It also covers data-only atoms, the shipped versus source-checkout split, and a module compiled in a VM started withERL_COMPILER_OPTIONS=deterministic(no source path), which must be held to the shipped rule.Falsified, each by disabling one rule and watching the named tests fail:
runtime_false_dependencyfails.attribute_list,keyword_value,map_valueandunresolved_modulefail.apply,ensure_loaded,is_struct, capture or remote-call rule off: that shape fails.unresolved_modulefails.Keyword.fetch!(:source)restored: the deterministic test fails.On the real task, a shipped
lib/imp/zz_probe.exwithimport NimbleParsec/defparsecandPlug.Conn.halt/1failed onNimbleParsec,NimbleParsec.Compiler,NimbleParsec.RecorderandPlug.Conn, and passed again once the file was removed. A full build underERL_COMPILER_OPTIONS=deterministic(beams without:source, confirmed) passes.Test-only uses
The tests name
Mint.TransportError,Finch.ErrorandDecimal. All three are covered by the declarations above.LLMDB.Modelingepa_confidence_frontier_test.exsasserts what ReqLLM returns. It is a test fixture, and LLMDB arrives through ReqLLM.Not changed
test/dependency_advisory_mitigation_test.exsholds cowboy at 2.16.0 or later. plug_cowboy's requirement (cowboy ~> 2.7) does not express that floor, so the test stays as the guard.Gates (local, at the pushed head)
mix format --check-formatted: cleanmix compile --warnings-as-errors: clean in dev and testmix dialyzer:Total errors: 143, Skipped: 143, Unnecessary Skips: 0, passedMIX_ENV=test mix quality.check: exit 0, includingEvery application Imp names is declared in mix.exs.mix package.check: exit 0,14 tests, 0 failures,clean-room package proof passedDocumentation contract, public_api_manifest, package_contract, the new check and demo-plug tests, and the affected ACP/MCP/GEPA tests:
142 tests, 0 failures (1 excluded)Full suite, at the first commit:
59 doctests, 9 properties, 3591 tests, 18 failures, 13 skipped (147 excluded). All 18 failures came from the environment:tmp/dspy-parity-venv.Once those were provisioned, the 9 files that held the failures gave
51 tests, 0 failures (11 excluded). The full suite was not rerun after the second commit.