Style Policy¶
DocBrain's style policy is the set of rules that govern how documents look — what words to avoid, what sections every runbook must have, how deep heading nesting can go. The policy is enforced at three points:
- Draft generation — the LLM is prompted with the effective ruleset for the target space, so generated drafts are born compliant.
- Post-generation lint — every draft is linted against the same ruleset before it lands in review. Mandatory-severity violations block publish unless an admin explicitly overrides.
- Existing-content lint —
/api/v1/quality/lintand the Quality Score page surface rule violations in the corpus, so operators can prioritise cleanup.
This page covers how the policy is structured, where it comes from, and what an operator needs to know about how changes propagate. For the API surface (CRUD endpoints, request/response shapes), see the API Reference.
Layered model¶
DocBrain rules sit at two scopes:
| Scope | Applies to | Set by |
|---|---|---|
global |
Every draft and document in the corpus | Admin (UI / API / YAML import) |
space_override |
Every draft and document in one specific space | Admin (UI / API / file-based puller) |
When a draft is generated for a space, DocBrain computes the effective ruleset for that space:
A space override may shadow an overridable global by the same
(rule_type, name) identity. It may not shadow a mandatory global —
the resolver drops the override and logs the attempt. This is how
admins encode "every team can tune the heading depth, but the
brand-mandated terminology rule is non-negotiable."
Mandatory vs. overridable¶
Every rule has an enforcement_level:
overridable(default) — a space can override this rule. Teams have flexibility; the global is a baseline.mandatory— a space cannot override this rule. The puller rejects any file-based attempt to weaken it; the resolver drops any in-memory override attempt; the DB has a trigger that raises an exception on a directDELETEof a mandatory row. This is reserved for org-wide policies that must hold everywhere (e.g. PII-language rules, compliance terminology).
Mandatory rules can only be created or modified through the admin API by an admin role. The file-based puller is intentionally limited to overridable rules.
Where rules come from¶
There are three sources of style rules. They all write to the same underlying table; the difference is who controls them.
1. Admin UI (/admin/style-rules)¶
Hand-crafted rules created by an administrator. Useful for one-off
rules, mandatory rules, and rules with severity=error that block
publish.
2. YAML import (POST /api/v1/style-rules/import)¶
Bulk-import a ruleset from a YAML file. Useful for seeding a new deployment from a curated template, or for keeping a version-controlled copy of the ruleset checked into a config repo.
3. File-based puller (.docbrain/style.md)¶
The puller monitors a registered source repository and syncs the rules
declared in its .docbrain/style.md file on a schedule. This is the
"GitOps for style policy" path — the policy file lives in the repo
that owns the team's standards, change review goes through normal
PR review, and DocBrain pulls the latest version.
See the working example
for a complete .docbrain/style.md you can copy.
Registering a source¶
Admin → Style Policy in the sidebar (or /admin/policy-file-sources).
Click "Register a source" and provide:
- Repository URL — the HTTPS URL of the repo containing
.docbrain/style.mdat its root. - Declared scope — the space this repo is authorized to publish
policy for. Use a space name (e.g.
ENG) for space-scoped rules, orglobalfor org-wide overrides.
The puller verifies that the file's frontmatter docbrain_scope matches
the registered scope before applying any rules — a repo registered for
ENG cannot smuggle rules into HR.
Branch policy (v1)¶
The puller fetches .docbrain/style.md from the repository's default
branch only. This is intentional: the trust model assumes the
customer has branch protection on the default branch, so policy
changes go through review. Feature branches typically don't have the
same protection, so accepting non-default refs would let any team
member with push access deploy policy bypassing review.
Non-default branch support is on the roadmap with additional audit-trail safeguards.
Scheduled pulls¶
The scheduler runs every POLICY_FILE_SYNC_INTERVAL_SECS seconds
(default 900s = 15 minutes). Each tick sweeps every enabled=true
source, fetches its .docbrain/style.md, parses, and upserts the
declared rules. Per-source failures are isolated — a 404 or a transient
network error on one repo doesn't abort the sweep, and the failure
surfaces on that source's row as last_error.
You can disable the scheduler by setting POLICY_FILE_SYNC_INTERVAL_SECS=0.
Manual pulls via the admin "Sync now" button still work when the
scheduler is disabled.
How rule changes propagate¶
This is the part operators most often ask about.
Single-replica deployments. When an admin creates, updates, or deletes a rule via the API or UI, the server's in-memory rule cache reloads immediately after the database commit. The next draft request sees the new rule. There is no cache TTL.
Multi-replica deployments. Each replica has its own in-memory rule cache. When an admin's PATCH lands on Replica A, only Replica A reloads — Replica B's cache stays stale until one of:
- Replica B handles its own write (the next admin action that lands on it)
- Replica B's scheduled-pull tick processes a source and triggers a reload
- Replica B restarts
There is currently no cross-replica invalidation broadcast. This is a known limitation. Operators running multi-replica deployments who need guaranteed cross-pod consistency should restart the deployment after any policy change until cross-replica invalidation ships.
What's recorded for every draft¶
When a draft is generated, DocBrain freezes a snapshot of the rules
that were effective at that moment into autopilot_drafts.style_snapshot.
This means:
- An audit of any past draft can show exactly which rules applied.
- A rule change in the future doesn't retroactively re-judge an old draft.
- The
severityandenforcement_levelof each rule at the time are preserved.
Failure modes¶
The puller has explicit semantics for every failure class. None of these silently corrupt your ruleset.
| Failure | Effect |
|---|---|
| Network / 5xx / timeout | Existing rules left in place. last_error set. |
| 401 / 403 (auth) | Existing rules left in place. last_error set. |
| 404 (file deleted upstream) | Rules previously pulled from this source are deleted. (Team retracted policy.) |
| Oversized file | Existing rules left in place. last_error set. |
| Scope mismatch (file says HR, registered as ENG) | Existing rules left in place. Logged as security event. last_error set. |
| Tries to define a mandatory rule | Existing rules left in place. Logged as security event. last_error set. |
The two security-event rows (scope mismatch and mandatory smuggling) are the load-bearing trust boundaries of the file-based puller. They ensure a compromised or hostile policy file cannot escalate a rule's authority or cross scope boundaries.
Configuration reference¶
| Setting / env var | Default | Effect |
|---|---|---|
Setting style.policy_file_max_bytes |
262144 (256 KiB) |
Maximum file size the puller will accept. Larger files are rejected. |
Env POLICY_FILE_SYNC_INTERVAL_SECS |
900 (15 min) |
Scheduled-pull interval in seconds. 0 disables the scheduler. |
For the full set of style-rule-related API endpoints, see the API Reference.