Model Sanitization Workflow
Trigger: sanitize directive, or any time the user asks to “clean up the model”, “fix model issues”, or “check document quality”.
This workflow applies semantic rules that go beyond schema validation — it detects documents where content is placed in the wrong tag type or where relationships are expressed as prose instead of structured references. Unlike lint, it does not call get_system_status; it reads documents directly and applies the rules below.
Rules
Each rule has a detection hint (how to spot the problem) and a fix (what to change).
Rule S-1 — Policy that is actually a requirement
Detection: A {% policy %} block whose body describes a constraint, invariant, or property of the system — not a reactive “when X, then Y” rule. These often read as declarative statements of fact or obligation.
Hint phrases: “is stored”, “must be”, “are encrypted”, “is limited to”, “only”, “always”, “never” — without a causal trigger clause.
Example (wrong tag):
\{% policy id="local-storage" %\}All data is stored locally.\{% /policy %\}Fix: Convert to \{% requirement %\} with an appropriate id, priority, and optionally scope. Remove the \{% policy %\} block.
\{% requirement id="local-storage" priority="must" %\}All data must be stored locally on the user's device.\{% /requirement %\}Rule S-2 — Policy without structured event and action links
Detection: A \{% policy %\} block whose body describes a reactive flow in prose — “when X happens, Y is triggered” — but whose source and reaction attributes are missing, set to TODO, or do not match the bare name of a declared API item.
Hint phrases: “when”, ”→”, “automatically”, “triggers”, “is triggered”, “upon”, “after”, “on”.
Example (missing links):
\{% policy id="auto-transcribe" %\}When SessionEnded → automatically trigger transcription.\{% /policy %\}Fix: Add source (referencing the declared \{% event %\}) and reaction (referencing the declared \{% action %\}) attributes. If the referenced event or action does not yet exist in an \{% api %\} block, add it there first.
\{% policy id="auto-transcribe" source="session-ended" reaction="start-transcription" %\}When a session ends, transcription is automatically started.\{% /policy %\}If the required event or action does not exist anywhere in the domain, create it in the appropriate \{% api %\} block before writing the policy reference.
Rule S-3 — Root tag id uses a type prefix
Detection: The root document tag (\{% feature %\}, \{% domain %\}, \{% role %\}, etc.) has an id attribute that includes a type prefix — e.g. id="domain:conversation" or id="feature:structured-formats". The compiler rejects this because the root tag id must match the frontmatter id exactly.
Why it happens: Agents apply the in-document child-tag convention ({type}:{name}) to root tags, where it does not apply. Type prefixes are only for in-document child tags (req:, action:, policy:, etc.) — never for the root document tag.
Hint: The compiler error reads: Root tag ID "…" does not match frontmatter ID "…".
Example (wrong):
---id: conversation---\{% domain id="domain:conversation" scope="public agent" %\}Fix: Strip the type prefix so the root tag id matches the frontmatter id exactly.
\{% domain id="conversation" scope="public agent" %\}Rule S-4 — Error definition missing a code
Detection: A \{% error %\} tag has no code attribute. The compiler reports this as a required-field schema error on the path tag:error[name].code.
Hint: get_system_status() returns entries with path matching tag:error[...].code and message: "Required".
Example (missing code):
\{% error id="recording-failed" %\}Raised when the audio device cannot be initialised.\{% /error %\}Fix: Add a code attribute. Infer an appropriate value from the error name using HTTP-style conventions; use "TODO" only when no reasonable value can be determined.
| Error name pattern | Suggested code |
|---|---|
*-not-found | 404 |
*-failed, *-error | 500 |
*-invalid, *-rejected | 422 |
*-forbidden, *-denied | 403 |
*-conflict, *-duplicate | 409 |
| Anything else | "TODO" |
\{% error id="recording-failed" code="500" %\}Raised when the audio device cannot be initialised.\{% /error %\}Rule S-5 — Cross-document reference using wrong format
Detection: Cross-document references in context frontmatter or in tag attributes (domains, roles, feature, implements, ref, etc.) that use:
- An unqualified id — e.g.
memory-reflectioninstead ofdomain/memory-reflection - The
:separator instead of/— e.g.domain:memory-reflection,role:user
Hint: get_system_status() returns type: "link" warnings with messages like "context ref not found: \"X\"" where X is unqualified or uses :.
Example (wrong):
context: [memory-reflection, role:user]\{% feature domains="domain:memory-reflection" roles="user" %\}Fix: Qualify all cross-document refs using {type}/{id} format with / as separator. First confirm the target document exists by checking content/{type}/ or calling get_document. Apply the fix to both frontmatter and tag attributes in the same file in one pass.
context: [domain/memory-reflection, role/user]\{% feature domains="memory-reflection" roles="user" %\}Auto-fixable: Yes — when the target document exists and can be identified. If the ref cannot be resolved to a known document (the target file is missing), flag for manual review.
Procedure
-
Fetch current issues.
get_system_status()Use the returned issue list to scope the work — prioritise files that already have reported errors. If the result is empty, proceed to step 2 with no pre-scoped files.
-
Identify scope. If the user names a file or domain, restrict to that. Otherwise apply to all
content/files that have reported issues — including link warnings — or that contain\{% policy %\}blocks. -
Read each file in full before making any judgement — never edit blindly.
-
Apply each rule independently. A single policy block may trigger more than one rule.
-
For each violation, confirm the intended semantics with the user before rewriting — converting a policy to a requirement is a structural change.
-
Write corrected files one at a time. Apply all fixes for a file in a single write pass.
-
Report using the format:
Sanitize complete — {n} issues fixed, {m} flagged for review
Fixed: domains/session.domain.mdoc — S-1: converted policy local-storage → requirement local-storage domains/session.domain.mdoc — S-2: added source/reaction links to policy auto-transcribe
Flagged for review: domains/recording.domain.mdoc — S-2: policy cleanup-on-error references no declared event (manual check needed)Do’s and Don’ts
Do:
- Read each file in full before making any judgement
- Apply each rule independently — a single block may trigger more than one rule
- Confirm the intended semantics with the user before rewriting (converting a policy to a requirement is a structural change)
- Write corrected files one at a time with all fixes for a file in a single write pass
- Create missing events or actions in the appropriate
\{% api %\}block before writing policy references
Don’t:
- Edit files blindly without reading them first
- Auto-fix without user confirmation — structural changes require approval
- Mix fixes across multiple files in a single write pass
- Flag issues that can be resolved by checking existing declared API items
Definition of Done
- All applicable rules (S-1 through S-5) checked against scoped files
- All fixes confirmed by user before writing
- Sanitization report output with fixed and flagged counts