Configuration file
About 1554 wordsAbout 5 min
2026-09-30
The configuration file a policy repository keeps at its root: the kind files the policy commands load, the paths they read as trusted, the policies sigil check requires, and the level of each lint. It's written in YAML, JSON or TOML, with the same keys in each. The first line of each example attaches the schema.
# yaml-language-server: $schema=https://sigil.specht-labs.de/schema/config.json
kinds:
- ../vendor/deploy_approval.sigil
trusted: [platform/vocabulary]
require:
- policy: deploy.guardrails
trusted: [platform/deploy]
roots: ["payments.*", "checkout.*"]
- policy: access.guardrails
trusted: [platform/access]
roots: [access.main]
lints:
gated-deny: error{
"$schema": "https://sigil.specht-labs.de/schema/config.json",
"kinds": ["../vendor/deploy_approval.sigil"],
"trusted": ["platform/vocabulary"],
"require": [
{"policy": "deploy.guardrails", "trusted": ["platform/deploy"], "roots": ["payments.*", "checkout.*"]},
{"policy": "access.guardrails", "trusted": ["platform/access"], "roots": ["access.main"]}
],
"lints": {"gated-deny": "error"}
}#:schema https://sigil.specht-labs.de/schema/config.json
kinds = ["../vendor/deploy_approval.sigil"]
trusted = ["platform/vocabulary"]
[[require]]
policy = "deploy.guardrails"
trusted = ["platform/deploy"]
roots = ["payments.*", "checkout.*"]
[[require]]
policy = "access.guardrails"
trusted = ["platform/access"]
roots = ["access.main"]
[lints]
gated-deny = "error"Finding the file
| Name | Format |
|---|---|
sigil.yaml, .sigil.yaml | YAML |
sigil.json, .sigil.json | JSON |
sigil.toml, .sigil.toml | TOML |
check,eval,explainandtestlook for the file under all six names in the working directory, then in each parent, and use the one in the nearest directory that has one.- Two of them in the same directory are an error.
--confignames a file instead, anywhere and under any name. Its extension picks the format:.yamlor.yml,.json, or.toml. Any other extension is an error.- Without a file, there are no extra kind files, trusted paths or requirements, and every lint keeps its default.
- Paths in the file are relative to the directory it's in, wherever the command runs. An absolute path is used as it is.
- JSON is strict JSON: no comments and no trailing commas.
Schema
The file's JSON Schema is published at https://sigil.specht-labs.de/schema/config.json. One schema covers all three formats, so an editor completes the keys and lint names and flags a typo as you type.
A modeline comment, read by yaml-language-server and the editors built on it:
# yaml-language-server: $schema=https://sigil.specht-labs.de/schema/config.jsonThe top-level $schema key, read by editors natively:
{
"$schema": "https://sigil.specht-labs.de/schema/config.json"
}The tools accept $schema in every format and ignore it.
A #:schema directive on the first line, read by Taplo and the editors built on it:
#:schema https://sigil.specht-labs.de/schema/config.jsonThe schema is stricter than the tools in one respect: it wants a string where the tools also read a number or a boolean as one.
Keys
Every key is optional, and the file holds no others. A TOML require entry is a [[require]] table, or an inline table in a require array.
| Key | Holds |
|---|---|
kinds | Kind files outside the paths a command reads. A list, or a single path |
trusted | Files and directories every policy command reads as trusted, as --trusted does. A list, or a single path |
require | The policies sigil check enforces, one entry each |
lints | A map from lint name to level: off, warn or error |
$schema | The schema the file follows, for editors. The tools ignore it |
kinds:
- Every policy command loads them as if they were named with
--kind, after the ones--kindnames. See Kinds. - Listing every kind file of the repository lets a command run on one directory of it, such as
sigil check teams/payments. A kind file that is also among the paths is read once, and counted once. - A kind file that doesn't exist fails the command, naming the configuration file.
trusted:
checkreads them as it reads--trusted, and as the host'spolicy.Trustedreads a source: their documents resolve first, and a document anywhere else that defines one of their names is an error. Nothing is required of them.--trustedadds to them for one run.- They don't say where a required policy must come from; an entry's own
trusteddoes. eval,explainandtestread them too, as trusted sources, apart from files among their paths.- A path that doesn't exist, or holds no
.sigilfile, fails the command, naming the configuration file:
Error: sigil.yaml: the trusted path platform/vocabulary can't be read
What you can do
• trusted lists files and directories relative to sigil.yaml
Caused by
• stat platform/vocabulary: no such file or directoryrequire entries:
| Key | Holds |
|---|---|
policy | The required policy's name. Required; one name, not a pattern |
trusted | Files and directories the required policy comes from, as --trusted and the host's policy.From read it. A list, or a single path. Optional |
roots | Name patterns of the policies it applies to, as --policy does. A list, or a single pattern. Optional |
- Every root the entry applies to must invoke
policyunconditionally, the check a host makes withpolicy.Require. - Without
roots, the entry applies to every policy of the required policy's kind that no other policy invokes, apart from the required ones. - Name the roots. A policy that another policy invokes isn't a root, even when the invocation sits under a
when, so withoutrootsthe requirement doesn't reach it: ifevil.wrapinvokesevil.produnder awhen, onlyevil.wrapmust invoke the required policy, although a host can loadevil.prodas a root. - With
trusted, the required policy must be defined below this entry'strustedpaths, where the host reads it. One defined anywhere else, among the paths or below another entry'strusted, is an error at the entry. - A required policy applies only to roots of its own kind, so one file can hold the requirements of several kinds.
- A policy can be required once.
- A
trustedpath that doesn't exist, or holds no.sigilfile, is an error at the entry. - When the command reads the directory the configuration file is in, or one above it, every entry must apply: a required policy no document defines, a
rootspattern that matches nothing, androotsthat match no policy of the required policy's kind are errors at the entry. - When it reads only part of that directory, such as one team's,
rootspick among the policies it read, and an entry withouttrustedwhose policy isn't among them is skipped. eval,explainandtestread thetrustedpaths too, as trusted sources, apart from files among their paths, so a policy in the directory they read finds the required policies it uses.--requireon the command line replacesrequirefor that run, its policies coming from the--trustedpaths when there are any.--trustedalone keepsrequireand adds trusted paths, as the top-leveltrusteddoes.--policykeepsrequire, and narrows each entry'sroots, or its default roots, to the policies it matches; an entry whose roots it leaves out is skipped, and arootspattern matching nothing is then no error.
lints:
- Lints the file doesn't name keep their defaults; Lints lists them.
Errors
An unknown key, lint or level is an error at its line and column, with the nearest valid name, so a typo can't silently leave a setting at its default. The advice writes keys the way the file's format does. A TOML array has no position of its own, so an error about one, such as lints = ["gated-deny"], is at its key.
kinds:
- ../vendor/deploy_approval.sigil
require:
- policy: deploy.guardrails
root: ["payments.*"]Error: sigil.yaml:5:5: unknown key "root" in require[0]
What you can do
• did you mean "roots"?
• a require entry holds `policy:`, `trusted:` and `roots:`{
"kinds": ["../vendor/deploy_approval.sigil"],
"require": [
{"policy": "deploy.guardrails", "root": ["payments.*"]}
]
}Error: sigil.json:4:37: unknown key "root" in require[0]
What you can do
• did you mean "roots"?
• a require entry holds `"policy"`, `"trusted"` and `"roots"`kinds = ["../vendor/deploy_approval.sigil"]
[[require]]
policy = "deploy.guardrails"
root = ["payments.*"]Error: sigil.toml:5:1: unknown key "root" in require[0]
What you can do
• did you mean "roots"?
• a require entry holds `policy`, `trusted` and `roots`A required name that no document defines:
require:
- policy: deploy.guardrailError: sigil.yaml:2:13: deploy.guardrail is required, but no policy deploy.guardrail was found
What you can do
• did you mean "deploy.guardrails"?
• check reads a required policy from the entry's trusted: paths, or else from its paths{
"require": [
{"policy": "deploy.guardrail"}
]
}Error: sigil.json:3:16: deploy.guardrail is required, but no policy deploy.guardrail was found
What you can do
• did you mean "deploy.guardrails"?
• check reads a required policy from the entry's trusted: paths, or else from its paths[[require]]
policy = "deploy.guardrail"Error: sigil.toml:2:10: deploy.guardrail is required, but no policy deploy.guardrail was found
What you can do
• did you mean "deploy.guardrails"?
• check reads a required policy from the entry's trusted: paths, or else from its pathsTwo configuration files in one directory:
Error: sigil.yaml and .sigil.json in the working directory are both configuration files
What you can do
• keep one of them; a command reads one configuration file, the nearest