Tack.BoundaryDeclarations (pica v0.1.0)

Copy Markdown View Source

Reads the use Boundary declarations out of Elixir source, so a test can assert on the shape of the architecture's enforcement.

The boundary declarations fail in one direction only. A declaration that is too narrow fails loudly — a forbidden reference, and the gate's compile step goes red. One that is too wide compiles in silence, and nothing in the toolchain will ever report that more permission was granted than the code uses. So the options that make the compiler check less are pinned by the generated boundary declarations test against a list of sanctioned exceptions, and widening a deps:/exports: grant is gated at authoring time by .claude/hooks/waiver-gate.sh.

One widening arrives the other way round — by deletion rather than addition — so it is read by its own function rather than by the escape-hatch list: see strict?/1.

Only the declaration is read, never prose about one

A declaration runs from a line whose first token is use Boundary until its brackets balance on a line that does not run on — one ending in neither , nor :, since mix format may leave a bare use Boundary, or a bare key such as exports: on a line of its own. The body of every """ heredoc is skipped first. That matters more than it sounds: a moduledoc explaining why a module may never say exports: :all, a test module quoting every option it forbids, or an installer whose template strings hold whole declarations must all score nothing. A scanner that matched anywhere in the file would score each as a maximal grant.

Summary

Types

A use Boundary … declaration, flattened to one line.

A check-disabling option: the token to search for, and what it does.

Functions

Whether mix_exs_source actually runs the boundary compiler.

The use Boundary declarations in source, each flattened to a single line.

The check-disabling options used in source, as {token, effect} pairs.

Every check-disabling option this module knows about, as {pattern, token, effect}.

Whether any declaration in source exports everything with a bare exports: :all.

Every file under root that carries at least one use Boundary declaration.

Whether any declaration in source sets type: :strict.

strict?/1 for a file on disk.

Types

declaration()

@type declaration() :: String.t()

A use Boundary … declaration, flattened to one line.

hatch()

@type hatch() :: {Regex.t(), String.t(), String.t()}

A check-disabling option: the token to search for, and what it does.

Functions

compiler_configured?(mix_exs_source)

@spec compiler_configured?(String.t()) :: boolean()

Whether mix_exs_source actually runs the boundary compiler.

Comment lines are dropped first, because a mix.exs may also describe compilers: [:boundary] in a note above the dependency — a bare substring search passes on that prose after the setting itself is deleted, which is the single largest bypass in the scheme.

compiler_configured_in?(path)

@spec compiler_configured_in?(String.t()) :: boolean()

compiler_configured?/1 for a file on disk.

declarations(source)

@spec declarations(String.t()) :: [declaration()]

The use Boundary declarations in source, each flattened to a single line.

A declaration starts at a line whose first non-space token is use Boundary and ends when the brackets opened since then balance on a line ending in neither , nor :, so a multi-line declaration is returned whole — including mix format's shape of a key alone on its line with the value below — and a comment or moduledoc mentioning one is ignored.

declarations_in(path)

@spec declarations_in(String.t()) :: [declaration()]

declarations/1 for a file on disk.

disabled_checks(source)

@spec disabled_checks(String.t()) :: [{String.t(), String.t()}]

The check-disabling options used in source, as {token, effect} pairs.

Each is a place where the compiler was told to enforce less than it could.

disabled_checks_in(path)

@spec disabled_checks_in(String.t()) :: [{String.t(), String.t()}]

disabled_checks/1 for a file on disk.

escape_hatches()

@spec escape_hatches() :: [hatch()]

Every check-disabling option this module knows about, as {pattern, token, effect}.

exports_all?(source)

@spec exports_all?(String.t()) :: boolean()

Whether any declaration in source exports everything with a bare exports: :all.

Legal for the umbrella, and a trap for a sub-boundary: it makes every module public the moment it compiles, where an explicit list fails safe — a new module is private until a person admits it. exports: {:all, except: […]} is a narrowing of the same thing and does not count.

exports_all_in?(path)

@spec exports_all_in?(String.t()) :: boolean()

exports_all?/1 for a file on disk.

source_files(root)

@spec source_files(String.t()) :: [String.t()]

Every file under root that carries at least one use Boundary declaration.

strict?(source)

@spec strict?(String.t()) :: boolean()

Whether any declaration in source sets type: :strict.

The mirror of disabled_checks/1, and the reason it needs its own function: every option that function scans for is a token whose presence weakens the check, so its arrival shows up in a diff. type: :strict weakens by going away. Boundary's default is :relaxed, which checks calls into an external application only when the boundary already names some other module from it — so deleting one word from a pure core's declaration drops the "nothing outside the standard library" half of its guarantee, and the deletion compiles clean, passes the gate, and scores nothing in .claude/hooks/waiver-gate.sh, which ranks deps:/exports: entries and does not read type:.

strict_in?(path)

@spec strict_in?(String.t()) :: boolean()

strict?/1 for a file on disk.