Last updated September 27, 2026
Boundary Rules
A boundary rule is a dependency rule you declare over a board's nodes: an app's modules on its app board, or your systems on the root board. The code plugin checks app board rules against the repo on every sync, and ProvenMap checks root board rules against the root board's edges. Use rules to write down the layering and data ownership you intend, then see exactly where your systems or your code cross them.
Declare a rule
Rules live on a board's Aspects screen, under the Boundaries tab. A rule's nodes come from the board you declare it on. To rule over the nodes inside a layer, open that layer first; to rule over your systems, open the root board.
- Open Aspects on the app board, layer or root board, and choose the Boundaries tab.
- Choose Add rule. The New rule form opens beside the list.
- Enter a Name. The Slug fills in from the name. Change it now if you want to, because it's fixed once saved: the plugin and work items refer to the rule by its slug.
- Choose a Kind and pick the nodes on each side (below). The form reads the rule back as a sentence, such as "Only Ordering Infrastructure may depend on Ordering DB".
- Add a Rationale: why the boundary exists.
- Choose Add rule, then Save changes at the top of the screen.
Save changes saves your rule edits into your working copy, like other board edits. On an app board, a new rule shows Not yet evaluated until the next sync checks it. On the root board, it's checked as soon as it's saved.
The two kinds
Over a store node, Only … may depend on is data ownership: only the nodes you allow may use that store. The node pickers list every node on the board, including stores and external systems, and each side takes up to 50 nodes.
From chat or the architect plugin
Chat and the architect plugin can declare rules too. Ask for one in chat or in /board, and the rule is written with the author_boundary_rules tool. An external agent with a read + write MCP token uses the same tool.
Rules between systems
The root board's nodes are your systems: services, stores, brokers and the apps they run as. Rules declared there say how systems may depend on each other. For example: "Only Ordering API and Order Processor may depend on orderingdb", or "Web App must not depend on Payment Processor".
On the root board, the Aspects screen shows Boundaries beside Clients and Events, which fold what passes between your apps (see Aspects Across Systems). An app's tables, endpoints and pages stay on its own app board.
No repo is bound to the root board, so ProvenMap checks its rules itself. The evidence is the root board's edges and the calls your apps' code makes to each other, from each app's api.clients aspect. A rule is checked when you save it, each time a commit changes the root board, and each time an app's push lands. A new edge to a store shows up as a crossing as soon as you commit it, and a new call as soon as its app pushes. Each crossing names its evidence: the edge that crosses the rule, or the client that makes the call.
When /setup-workspace draws your landscape from a .NET Aspire AppHost, it also offers one data-ownership rule per database, naming the systems wired to it today. Pick the ones to declare; a database more than one system owns is worth a look on its own.
Drawing your landscape in chat ends with the same offer, once the landscape is saved: one data-ownership rule per store, naming the systems with an edge into it. Describe a separation you want, such as "the storefront never calls payments directly", and chat declares it as a rule too. A missing edge alone never becomes a rule.
See the rules as rings
The Boundaries tab has two views: List and Rings. Rings draws the board's rules as an onion of stacked rings, seen from above.
- The rings come from your rules. A Must not depend on rule puts the first nodes inside the second, so every dependency a rule allows points inward. The nodes your rules keep most independent, such as a domain model, sit at the centre.
- Owned resources stand outside the rings. The target of an Only … may depend on rule is drawn as a dashed card, next to the nodes allowed to reach it.
- Arrows are dependencies. A solid arrow is one a rule allows. A dashed red arrow with ✕ and a count is a crossing that breaks a rule. Hover a node to light its arrows; choose an arrow to open its rule.
- Two rules that contradict each other put nodes inside each other. Those nodes share one ring, and a warning above the rings names the rules.
The rings show only the rules declared on the board you're on. On an app board, rules declared on its layers are listed but drawn on their own layer's rings.
Read a rule's status
Each rule shows one of three statuses. Violated rules sort to the top of the list.
Open a rule to see its sentence, its status, when it was evaluated (and, on an app board, at which commit), its violations and its rationale.
Each violation is a crossing from one node to another, such as Ordering API → Ordering DB, with a count of the evidence behind it. Expand a crossing to see that evidence:
- An import: the file, then the file it imports
- A project reference (.NET): the project, then the project it references
- A board edge: the source node, the target node and the edge's relation
Each crossing shows up to 20 pieces of evidence and lists the rest as "more not sampled". The count always covers all of them.
Watch rules from the hub
Add the Boundaries card from the hub's Add widget menu, on an app's hub or the root hub. It counts the rules and how many hold, are violated or are not yet evaluated. Violated rules lead its list, the one with the most crossings first; open a row to go to the rule.
Edit or delete a rule
Open a rule and choose Edit or Delete. You can change the name, the kind, the nodes and the rationale, but not the slug. Save with Save changes, as when you declare one.
Changing the kind or the nodes resets the rule to Not yet evaluated. Its note reads "The rule changed; the next sync evaluates it." A result proven for the old nodes says nothing about the new ones. Renaming a rule or editing its rationale keeps its status.
The Boundaries tab on an app board also lists the rules declared on its layers. A rule declared on a layer has its name, kind and nodes edited from that layer; elsewhere, only its Rationale can change.
How the code plugin checks a rule
The code plugin checks every rule on a board at the end of each successful /sync of that board. There is nothing to configure. It checks three kinds of evidence:
- Imports between files, from the dependency graph that
/analyzebuilds - Project references in each
.csprojfile, for .NET code - The board's own edges, for nodes that have no files, such as databases and external systems
/sync prints the result, then one line for each rule that is violated or not evaluated:
The plugin reports a violation and leaves the code alone; it never changes code to fix one. When you sync from a branch other than the one the board's binding is pinned to, the check is skipped.
Track a fix with a work item
A work item anchored to a rule tracks that rule's status. Open the rule and click the ⊕ in its header to add it to the work item you're editing. The rule's row then shows the work items anchored to it.
When a sync moves the rule between Holds and Violated, the work item shows ≠ Drifted, so you know to review it. A developer who pulls the work item with /work-items is told the change is done when /sync reports the rule holding. Each sync until then lists the imports and project references that still break it.
Publish rules with a board
When you publish a board, the wizard's Contents step lists your rules under Aspects, in the Boundaries group. A reader sees each rule you select: its sentence, its status, when it was evaluated, its rationale, and each crossing with its count. The file paths, projects and commits behind a crossing are never published. Publishing Boards covers the wizard.
FAQ
Why does a rule still say Not yet evaluated after a sync?
Hover the mark or open the rule to read the note. It gives one of three reasons:
- The rule names a node that isn't on the board. On the root board, this is the only reason.
- The repo has no dependency graph yet. Run
/analyze, then/sync. - The rule changed after the sync read it. The next sync checks it.
Why does a rule hold when I know the code crosses it?
A file counts for a node only when the board maps that file to the node. An import from a file that no node on the board covers isn't counted against any rule. If the board is behind the code, run /analyze and /sync again.
What happens to rules when I remove the app?
Remove app keeps them, because they're your declarations over the layer's nodes and Remove app keeps those nodes. A plain layer has no Aspects screen, so the rules show again when you make the board an app again. Delete app removes them with the board. See Removing or deleting an app.