boundry 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -28,6 +28,25 @@ diagram (LikeC4) ──► boundary model ──► dependency-cruiser rules
28
28
  Elements without a `folder` (actors, external systems, notes) are ignored, so a
29
29
  rich communication diagram and an enforcement diagram can be the same file.
30
30
 
31
+ ### Governing a whole root (opt-in)
32
+
33
+ By default a folder no element maps to is *ignored* — any module may import it.
34
+ That is what keeps a communication diagram usable as an enforcement diagram, but
35
+ it means brand-new, unmodelled code is free to import. Declare a root as fully
36
+ governed and the whole tree becomes the universe instead:
37
+
38
+ ```likec4
39
+ system app 'App' {
40
+ metadata { governRoot 'src' }
41
+ }
42
+ ```
43
+
44
+ Now importing anything under `src/` that no module claims is a **violation**, and
45
+ `check` **warns** about code under the root that no module covers. That is the
46
+ mirror of the zero-files warning: the model failing to cover the code is as much
47
+ a gap as the code failing to back the model. Nothing changes unless you declare
48
+ it.
49
+
31
50
  ## See it
32
51
 
33
52
  The diagram you draw *is* the whole spec. Below is the example architecture that
@@ -53,6 +53,21 @@ function buildForbiddenRules(model) {
53
53
  });
54
54
  }
55
55
  }
56
+ // A declared govern root makes the whole tree the universe: territory no
57
+ // module claims is forbidden, not free. Additive — the pair rules above still
58
+ // decide every mapped-to-mapped edge on their own.
59
+ if (model.governRoot) {
60
+ const claimed = model.modules.map((m) => folderToRegex(m.folder));
61
+ for (const from of model.modules) {
62
+ rules.push({
63
+ name: `boundary:${from.id}->unmapped`,
64
+ comment: `${from.title} may not depend on code under '${model.governRoot}' that no module claims`,
65
+ severity: 'error',
66
+ from: scopeOf.get(from.id),
67
+ to: { path: folderToRegex(model.governRoot), pathNot: claimed },
68
+ });
69
+ }
70
+ }
56
71
  return rules;
57
72
  }
58
73
  /** First enforcer adapter: targets dependency-cruiser for TypeScript. */
@@ -93,6 +108,24 @@ module.exports = {
93
108
  warnings.push(`module '${mod.title}' maps to '${mod.folder}', which matched 0 source files`);
94
109
  }
95
110
  }
111
+ // The symmetric guard: code under a declared govern root that no module
112
+ // claims. The model not covering the code is as much a gap as the code not
113
+ // backing the model.
114
+ if (model.governRoot) {
115
+ const rootPrefix = `${folderPrefix(model.governRoot)}/`;
116
+ const claimed = model.modules.map((m) => `${folderPrefix(m.folder)}/`);
117
+ const unclaimed = new Set();
118
+ for (const source of seen) {
119
+ if (!source.startsWith(rootPrefix))
120
+ continue;
121
+ if (claimed.some((prefix) => source.startsWith(prefix)))
122
+ continue;
123
+ unclaimed.add(source.slice(0, source.lastIndexOf('/')));
124
+ }
125
+ for (const dir of unclaimed) {
126
+ warnings.push(`'${dir}' is under govern root '${model.governRoot}' but no module claims it`);
127
+ }
128
+ }
96
129
  return { ok: violations.length === 0, violations, warnings };
97
130
  }
98
131
  }
@@ -91,7 +91,14 @@ export class LikeC4Visualizer {
91
91
  const model = await likec4.computedModel();
92
92
  const modules = [];
93
93
  const folderIds = new Set();
94
+ let governRoot;
94
95
  for (const el of model.elements()) {
96
+ // Opt-in: any element may declare the code root as fully governed. It
97
+ // needs no `folder` of its own — it is a declaration, not a module.
98
+ const rootMeta = el.getMetadata('governRoot');
99
+ const declaredRoot = Array.isArray(rootMeta) ? rootMeta[0] : rootMeta;
100
+ if (declaredRoot && !governRoot)
101
+ governRoot = declaredRoot;
95
102
  const meta = el.getMetadata('folder');
96
103
  const folder = Array.isArray(meta) ? meta[0] : meta;
97
104
  if (folder) {
@@ -112,7 +119,7 @@ export class LikeC4Visualizer {
112
119
  allowed.push({ from, to });
113
120
  }
114
121
  }
115
- return { modules, allowed };
122
+ return { modules, allowed, governRoot };
116
123
  }
117
124
  /**
118
125
  * Deterministically strip `#proposed` markers from the diagram source,
@@ -19,6 +19,13 @@ export interface AllowedEdge {
19
19
  export interface BoundaryModel {
20
20
  modules: Module[];
21
21
  allowed: AllowedEdge[];
22
+ /**
23
+ * Optional code root declared fully governed. Every folder under it is
24
+ * expected to be modelled, so importing territory no module claims is
25
+ * forbidden rather than free. Omitted (the default) = unmapped folders are
26
+ * ignored, which keeps a communication diagram usable as an enforcement one.
27
+ */
28
+ governRoot?: string;
22
29
  }
23
30
  /**
24
31
  * Edges allowed at `head` that were not allowed at `base`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "boundry",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Compile a C4 architecture diagram into a deterministic dependency linter. Deterministic architectural guardrails for AI agents and humans.",
5
5
  "type": "module",
6
6
  "license": "MIT",