> Documentation version: 3. [HTML page](policy.html)

<a id="check-repository-source-policy"></a>

# Check repository source policy

` roc-policy ` is a precompiled, read-only source-policy utility. It checks
repository conventions that compiler diagnostics, formatting and behavioral
tests do not establish. The tools release builds binaries for x86\_64 Linux,
ARM64 Linux and Apple Silicon macOS. The Linux binaries are static musl
executables. Building and testing the implementation happens upstream; source
checking downstream requires no Roc compiler, Python, Git or Nix.

<a id="risk-policy-evidence"></a>

## Risks and controls for source-policy evidence

A rule can inspect the wrong files, an override can hide a violation, or an
incomplete scan can appear successful. Make rule scope and analysis completeness
observable. Explicit roots, validated profiles, explained effective configuration
and stable findings let maintainers inspect what the checker selected. Malformed
configuration and incomplete analysis fail separately from policy findings;
fixtures verify exclusions and conflicting settings. A clean source-policy report
establishes only the rules actually checked. It is not behavioral test coverage
or proof that the product has no security defects.

<a id="declare-policy-in-blueprint"></a>

## Declare policy in Blueprint

The default authority is ` repository_policy ` in ` Blueprint.roc `. Schema 2 composes
shared defaults with repository rules and explicit overrides. The surrounding
file remains normal Roc configuration; add the binding beside its existing
` config `, without replacing its Blueprint platform declaration:

``` roc
tooling = { compiler: "nightly-<date>-<commit>" }

repository_policy = {
    schema_version: 2,
    roots: ["package", "examples", "scripts", "docs"],
    exclude: ["**/generated/**"],
    test_roots: ["package"],
    profiles: [{ name: "base", builtin: "roc-package", version: 1 }],
    rules: [
        { id: "project/compiler", kind: CompilerPin, include: ["scripts/**"], value: tooling.compiler },
        { id: "project/no_internal_imports", kind: ForbiddenImport, include: ["package/core/**"], name: "support/Internal" },
        { id: "prose/worth_noting", kind: Phrase, include: ["docs/**"], channels: [Prose], phrase: "it is worth noting" },
    ],
    overrides: [
        {
            rule: "base/spelling",
            reason: "Published ecosystem name",
            changes: { accepted_words: { add: ["rocpkgs"] } },
        },
        {
            rule: "base/public_docs",
            include: ["package/fixtures/**"],
            reason: "Imported fixtures preserve upstream source",
            changes: { level: Off },
        },
        {
            rule: "prose/worth_noting",
            include: ["docs/quotations/**"],
            require_match: False,
            reason: "Quoted source retains its original wording",
            changes: { level: Warning },
        },
    ],
}
```

An environment may reference the same ` tooling.compiler ` when declaring its
compiler. The utility resolves local constants, forward references, record puns
and field access without evaluating Blueprint. It does not prove that arbitrary
Blueprint expressions actually use those facts. Configuration composition is
implemented by the policy resolver, separate from generic constant extraction.

Supported data are literal records, lists, strings, booleans, plain decimal
integers and bare tags, plus references, field access and parentheses. Strings
support Roc escapes including Unicode scalars. Calls, arithmetic, functions,
record updates, interpolation and imported values in the selected dependency
graph are rejected. Unrelated functions and imports may remain in the file.
The complete file must parse without diagnostics. Duplicate definitions/fields,
cycles, unknown fields, irrelevant options and resource-limit violations fail.

The generic constant-data module has no profile loading or policy-specific
behavior. It limits each source to 1 MiB, declarations/work to 10,000, depth to
64 and expanded-value accounting to 1 MiB. Policy composition admits at most
32 profiles, 10,000 final rule instances and 10,000 overrides.

<a id="shared-profiles-and-rule-identities"></a>

## Shared profiles and rule identities

A rule’s ` kind ` selects an implementation. Its required ` id ` identifies one
configured policy instance. The diagnostic code identifies the finding category.
For example, two ` Phrase ` rules can share diagnostic code ` text/phrase ` while
having independently overridable IDs. IDs use slash-separated components of
letters, digits, dots, underscores and hyphens; empty, ` . ` and ` .. ` components
are invalid. Duplicate IDs fail instead of replacing earlier declarations.

Profiles load in declared order, followed by local rule declarations. Their rule
IDs are namespaced by the profile’s ` name `: rule ` spelling ` in profile ` base `
becomes ` base/spelling `. A local rule cannot accidentally replace that ID. Change
its settings through an explicit override instead.

` roc-policy list-profiles ` lists profiles embedded in the binary. The immutable
` roc-package ` version 1 supplies ` public_docs ` for ` package/**/*.roc ` and
` spelling ` for supported Roc/prose extensions. Both rules are intentionally
optional across repository layouts. Embedded profiles need no sidecar files;
the reviewed binary selects their bytes, and explanations include their SHA-256.
Unknown names and versions fail.

Alternatively, a profile descriptor may contain ` name `, repository-relative
` path `, ` definition ` and the reviewed lowercase 64-character ` sha256 `. The file
must define a bounded record such as:

``` roc
profile = {
    schema_version: 2,
    rules: [
        { id: "spelling", kind: Spelling, channels: [Comments, Prose] },
    ],
}
```

The loader verifies the exact file bytes before interpreting the chosen binding.
A missing file, symlink, unsafe path or mismatched digest fails; no download or
external hashing tool is invoked. Profiles contain rules only, without nested
profile loading, discovery roots or overrides. Profile paths are anchored at the
explicit repository root. This permits independent policy-data releases while
keeping profile selection, pins and repository decisions in Blueprint.

Shared profiles are reusable defaults that repository authors may override.
Mandatory organizational requirements require an independently trusted CI
policy/invocation. Selecting a profile in a repository-controlled Blueprint is
not evidence that such external requirements were enforced.

<a id="ordered-overrides"></a>

## Ordered overrides

Overrides target one exact rule ID. Every override requires a nonempty ` reason `;
unknown IDs and unknown or irrelevant changes fail. Without ` include `/` exclude `,
an override applies repository-wide. With file selectors, it applies to matching
paths. All matching overrides apply in written order, including global overrides:
later scalar values win, without hidden path-specificity precedence.

` level ` is ` Off `, ` Warning ` or ` Error `, defaulting to ` Error `. There is no separate
` enabled ` flag. Off suppresses the selected rule. Warnings are reported without
failing by default; set repository ` max_warnings: 0 ` to reject any warning, or a
nonnegative number to allow that many. Configuration errors, I/O failures and
incomplete analysis remain exit 2 regardless of rule level.

Collection changes use explicit operations:

``` roc
changes: {
    accepted_words: { add: ["rocpkgs"], remove: ["oldterm"] },
    channels: { replace: [Comments, Prose] },
    corrections: { add: [{ word: "mispeling", replacement: "misspelling" }] },
}
```

` replace ` discards inherited values and cannot be combined with ` add ` or ` remove `.
` add ` extends sets without duplicates; ` remove ` subtracts matching entries.
Vocabulary and correction keys fold ASCII case; paths remain case-sensitive.
Adding a correction replaces the inherited correction with the same normalized
key. Removing a custom correction exposes the bundled correction, if any; use
` accepted_words ` to accept the word instead. Contradictory operations and duplicate
keys in one layer fail. There is no implicit list concatenation or deep merge.

Overrides may change level and the options supported by their rule kind.
Repository-wide overrides may also change rule ` include `/` exclude ` collections
and ` require_match `. File-scoped overrides cannot change rule IDs, kinds, selectors or the
aggregate rule ` require_match ` assertion. Discovery roots and profile pins are not patchable through rules.

Rules require an applicable source file by default. An intentionally optional
rule may declare ` require_match: False `, useful in profiles shared across
different layouts. Overrides likewise require a selected target/file by default;
set their ` require_match: False ` for intentional future or optional exceptions.
This catches renamed paths and stale exceptions without making every shared rule
mandatory. It checks applicability, not whether an exception suppressed a finding.

<a id="commands-and-paths"></a>

## Commands and paths

``` sh
roc-policy check --root .
roc-policy check --root . --format json
roc-policy list-profiles
roc-policy explain-config --root .
roc-policy explain-config --root . --path package/Thing.roc
roc-policy manifest --root . > expected-tests.json
roc-policy run-tests --root . --compiler /absolute/path/to/roc
```

` --config ` selects a file relative to the root and ` --definition ` selects a
binding; defaults are ` Blueprint.roc ` and ` repository_policy `. All configured
commands use this same authority. ` manifest ` and ` explain-config ` emit JSON.
Explanation includes profile digests, rule IDs, origins, effective options,
selector decisions and ordered override traces. Without ` --path ` it shows global
settings; a path shows effective settings and why rules are selected, excluded
or disabled. It need not open the source file to explain its configuration.

` roots ` and ` test_roots ` are exact repository-relative files/directories.
Schema 2 rule and override selectors use root-relative slash-separated globs:
` * ` matches within one component, ` ? ` matches one Unicode scalar, and a complete
` ** ` component matches zero or more path components. Thus ` **/*.roc ` includes
root files and ` package/** ` includes the directory and descendants. Literal
patterns match exactly. Dotfiles match normally. Unsupported bracket/brace,
negation and escape syntax, absolute paths, controls and dot segments fail.
Patterns and matched paths are bounded to 4096 bytes and 128 components.

Global ` exclude ` prunes discovery before opening sources or descending into
excluded directories. Rule exclusions affect that rule alone. File overrides do
not resurrect globally excluded files. There is no implicit Git ignore behavior,
parent-configuration cascade or dependence on the current directory once the
explicit root is fixed. Global exclusions also apply to test discovery, but do
not bypass explicit configuration/profile reads or their integrity checks.

Discovery is deterministic and overlapping roots are deduplicated. Sources are
` .roc `, ` .md `, ` .markdown `, ` .adoc `, ` .txt ` and ` .rst `; accounting uses only ` .roc `.
Selected sources reject symlinks and special files. Input files are limited to
8 MiB and inventory traversal to 100,000 paths.

Exit 0 means completed checks within the warning allowance, 1 means error-level
findings or too many warnings, and 2 means invalid input or incomplete analysis.
Diagnostic JSON uses schema 2 and includes ` rule_id `, ` level `, ` origin ` and ` trace `
alongside the category ` rule `. Text locations use one-based Unicode scalar
columns; JSON retains half-open UTF-8 byte spans.

<a id="rules"></a>

## Rules

` CompilerPin `, ` NoCompilerPin ` and ` Dependency ` inspect app/package/platform
headers, not lookalike fields in bodies, comments or strings. A required
compiler/dependency rule aimed at an exact entrypoint reports a missing header.
Directory-wide required rules apply only to header-bearing entrypoints, allowing
ordinary helper modules. They fail configuration if no entrypoint is applicable.
A dependency value is an exact decoded string; alternate relative spellings
are distinct values.

` ForbiddenImport ` matches the written path or its dot/slash descendants, ignoring
trivia and preserving aliases' original paths. It does not resolve bindings,
reexports, indirect calls or a complete project dependency graph.

` PublicDocs ` identifies explicit module exposures and filename-based nominal
modules, including nested public associated definitions. It requires nonempty
attached ` ## ` documentation, supporting documentation before an annotation or
its definition. Private helpers and local declarations are excluded. Package
uppercase exposure inventories refer to other module files; select those source
files for checking. Unresolved imported/qualified/wildcard public surfaces return
` documentation/incomplete `, never an assertion that they are documented.

` Spelling ` embeds the pinned typos v1.44.0 correction map (93,861 entries)
through a Roc file import. It requires no dictionary file, download or Rust
toolchain at runtime. This is correction lookup rather than unknown-word
detection; it does not reproduce the typos CLI heuristics or dialect rules.
The exact upstream CSV, MIT notice, provenance and offline verification script
are maintained in ` tools/roc-policy/vendor/typos `. ` roc-policy --licenses `
prints the embedded notice, which is also included among release assets.
` channels ` selects ` Comments `,
` Identifiers `, ` Strings ` and ` Prose `; defaults are comments, identifiers and prose.
Identifiers split snake case, camel case, acronym transitions and digits.
` accepted_words ` exempts word components and ` accepted_identifiers ` exempts whole
Roc identifiers, without accepting their components elsewhere. Add corrections
with ` corrections: [{ word: "mispeling", replacement: "misspelling" }] `; entries
replace the same bundled key. Keys must be single words. Case folding is ASCII;
Unicode bytes and source offsets are preserved. String channels inspect raw
string parts rather than evaluating escape sequences.

` Phrase ` requires ` phrase `, optionally ` replacement `, and uses the same channels
as spelling. Exceptions target the particular phrase rule ID. Whole-word matching folds ASCII case and normalizes whitespace
across consecutive comment/documentation lines; internal and surrounding
punctuation must match. Patterns must contain a matchable word. There are no
implicit banned phrases. A policy enforces chosen language conventions, not
inference about authorship or prose quality.

<a id="test-accounting"></a>

## Test accounting

` manifest ` discovers static file-level and associated-block ` expect ` declarations
under ` test_roots `, defaulting to ` roots `. It emits schema 1 JSON with ` files `,
each containing ` path `, exact ` source `, and ` tests ` with byte ` start ` and ` end `.
It is a source inventory, with no execution statuses. Inline/local expectations
are valid assertions but are not separate required static tests. A static test
sharing its starting line with another expectation is rejected because the
compiler’s line output cannot distinguish them. Empty selected static inventories
fail explicitly.

` run-tests ` is an optional discovery-driven runner requiring an explicit compiler
path. It invokes ` roc test <absolute-source-path> --verbose ` for each inventoried
file, leaving normal compiler caching enabled. Optional ` test_main ` names a
relative application file used to resolve package aliases with ` --main=... `.
For every static test, exactly one ` PASS: <absolute-source-path>:<line> ` record
must be reported. Tests in imported local modules and executed inline assertions
may add records; they do not inflate the selected file’s static count. The total
number of PASS records must also agree with the compiler’s success summary.
Missing or duplicated required locations fail, even when the overall count is
unchanged. A successful-looking output fragment cannot override a failed process.

Normal cached results are accepted. Cache correctness is the compiler’s
responsibility; this utility does not routinely pass ` --no-cache `. Every child
must exit successfully within 60 seconds and uses bounded output capture.
Inventoried sources are checked again after all invocations; changed sources,
ambiguous locations, malformed output and incomplete parses fail. No shell
command is constructed.

This checks compiler-reported source lines, not native test IDs, exact span
receipts, code coverage or authenticated execution provenance. It does not prove
that a different CI entrypoint selects these tests. It discovers and invokes
each selected test-bearing file itself, so large import graphs can cause repeated
compiler invocations; normal caching reduces that cost. The adapter is tested
against the compiler its tools release records and depends on that compiler’s
human-readable verbose output. An incompatible output format fails explicitly.

The runner can execute project code and invoke the compiler’s normal dependency
behavior. Use it in unprivileged jobs. ` check ` and ` manifest ` require no compiler,
execute no project code, and invoke no external helpers.

<a id="distribution-boundary"></a>

## Distribution boundary

Install a reviewed tools release and verify its content digest before invoking
it. Blueprint can manage the binary through ` Programs ` and ` Fetch `, with its
bytes pinned by ` Blueprint.lock `; see
[utility provisioning](pure-package.md#adopt-a-pure-package-without-nix).
Content hashes establish expected bytes, not build provenance.

