enola-guides 0.2.1 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b0ff999485e94088e964c4dd465bb5c1a26fea1a6dece3ed79b301e644786a8
4
- data.tar.gz: 2c015c5cb721592b33c66e274108ead2f537a50e706d0a18d7a97880e9f78664
3
+ metadata.gz: b72117b0db442295ba7133b5328977a4e182bea8d32551298d831f804de8d25e
4
+ data.tar.gz: 1e30af4f9ea75f6562b2084f62dca453ac7036853d8fbba43924eb56ff54d3b5
5
5
  SHA512:
6
- metadata.gz: 581a958308b2e03bd6014a30f73007e7b9e6f70cfc5d97dad82d5e733f520c87b557983d3da041ee4234c7d392d510c67ddcf31a145d166909ad791e95ca187c
7
- data.tar.gz: b2b684408b5ec182a1edb87f36483f5159af9520e60c144062b1d9e28f550c4a4176f2c3b256e73a8681b1c776a89aab5999b9816c310692331685e44b14e604
6
+ metadata.gz: 3136e1557b6cee67baeaf92112514cbffd36fe9af031784557986a97903889636dedcc7bd9b107264af190d9c5ded9a40642df5ec8642404aa16b78c7cc5fa01
7
+ data.tar.gz: c9f373fb525b4ad29c5edd78791abce11cf4e2feaedd3d71625e5b618393494908dcb297132e128e2af505c33cd68758fc6d4c3dd3c73c8c168f47c2482d42ec
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 (2026-08-23)
4
+
5
+ The recipe catalogue munola binds: `data-ownership`, `api-boundaries`,
6
+ `background-work` and the `tenant-foreign-key` template beside
7
+ `ember-conventions`, each law an existing enola form with the number it held
8
+ at on a real monolith in the catalogue table; every file lints clean bound
9
+ to a Rails tree.
10
+
3
11
  ## 0.2.1 (2026-08-23)
4
12
 
5
13
  enola v0.4.4 released the constraints program the guides describe
data/README.md CHANGED
@@ -74,7 +74,7 @@ your agent's skills folder; the hooks they pair with are under
74
74
  ## Templates and recipes
75
75
 
76
76
  [`templates/`](templates): the CI check script and its comment renderer,
77
- a cluster configuration, a constraints starter. [`recipes/`](recipes/README.md):
77
+ a cluster configuration, a constraints starter. [`recipes/`](recipes/README.md) (the catalogue munola binds, each recipe with the number its laws held at):
78
78
  recipes a repository copies into its own `enola/recipes/` and binds in one
79
79
  line, starting with the Ember conventions and a house-recipe template;
80
80
  starting points the team reviews, never defaults the tool applies.
data/lib/enola-guides.rb CHANGED
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Enola
4
4
  module Guides
5
- VERSION = "0.2.1"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
data/recipes/README.md CHANGED
@@ -40,6 +40,27 @@ breach. Start with ratchet.
40
40
 
41
41
  ## The recipes
42
42
 
43
+ The catalogue munola binds with one command. Each law is an existing enola
44
+ form; each recipe is here because its laws held on a real Rails monolith at
45
+ a number a team can act on, given beside it.
46
+
47
+ | Recipe | States | Held at | Placement |
48
+ |---|---|---|---|
49
+ | `rails-conventions` (upstream) | jobs, models, serializers and components never render; policies and mailers never enqueue; request API stays in controllers | five of seven laws with zero exceptions, three advisory at 134 crossings | embedded in enola |
50
+ | `rails-strict` (upstream) | the above plus no cycles among the Rails roles and independent concerns | 0 of 127 concerns breach | embedded in enola |
51
+ | `vanilla-rails` (upstream) | the directories the vanilla arrangement leaves empty stay empty | an arrangement a team chooses | embedded in enola |
52
+ | `ember-conventions.yaml` | nine Ember conventions selected by what the code carries | the neutral eight hold; the house ones stay out | embedded in munola |
53
+ | `data-ownership.yaml` | readers read the store only | advisory; bind your query objects | embedded in munola |
54
+ | `api-boundaries.yaml` | the public API is reached only by controllers; a mutating action reaches a policy | 736 unauthorized mutations reported, advisory | embedded in munola |
55
+ | `background-work.yaml` | maintenance tasks live in their namespace and never reach controllers | 0 of 3,250 task call edges reach a controller | embedded in munola |
56
+ | `tenant-foreign-key.yaml` | a table carrying the tenant column carries the foreign key | 3,720 declared, 0 new | written by munola with the column filled in |
57
+
58
+ Two data-ownership laws, one owner per table and storage stays home, are
59
+ declarations rather than recipe rules for now: they live as worked examples
60
+ under [`examples/laws/`](../examples/laws/README.md) because recipe expansion
61
+ in the current release does not rebind the part a graph law names. They join
62
+ `data-ownership.yaml` when it does.
63
+
43
64
  - `ember-conventions.yaml`: nine Ember conventions a team can adopt as laws,
44
65
  selected by what the code carries (a decorator, a written field, a base
45
66
  class's module) rather than by where it sits. Two optional roles are
@@ -47,6 +68,17 @@ breach. Start with ratchet.
47
68
  tracked functions in your app) and `fixture-setup` (the entry points of
48
69
  your fixture library in tests).
49
70
 
71
+ - `data-ownership.yaml`: readers (your query objects, bound by you) reach
72
+ the store and nothing else.
73
+ - `api-boundaries.yaml`: the API layer is entered through controllers, and a
74
+ mutating action reaches a policy; bind `mutating-actions` to the part your
75
+ routes reach with POST, PUT, PATCH or DELETE.
76
+ - `background-work.yaml`: maintenance tasks live in the `Maintenance`
77
+ namespace the runner resolves, and never reach controllers.
78
+ - `tenant-foreign-key.yaml`: a template; replace `TENANT_COLUMN` and
79
+ `TENANT_TABLE` (munola's generator does) and every table carrying the
80
+ column must carry the key.
81
+
50
82
  Laws whose far end is a helper of your own (a promise-unwrapping getter
51
83
  helper, an async-relationship reader) belong in your repository's recipe
52
84
  beside these, with your names in them.
@@ -0,0 +1,25 @@
1
+ recipe: api-boundaries
2
+ roles:
3
+ - name: controllers
4
+ match: ["app/controllers/**"]
5
+ - name: policies
6
+ match: ["app/policies/**"]
7
+ - name: public-api
8
+ match: ["app/controllers/api/**"]
9
+ - name: mutating-actions
10
+ optional: true
11
+ rules:
12
+ - id: public-api-reached-only-by-controllers
13
+ protect: public-api
14
+ owners: [controllers]
15
+ via: calls
16
+ mode: advisory
17
+ because: "The API layer is entered through a request so that authentication, rate limits and serialization run once, at the edge; an internal caller that reaches an API class directly skips all three and nobody reading the API can see it."
18
+
19
+ - id: mutations-are-authorized
20
+ require_edge: mutating-actions
21
+ to: policies
22
+ direction: outbound
23
+ via: calls
24
+ mode: advisory
25
+ because: "Every write passes a policy before it touches a record. Bind the actions that routes reach with POST, PUT, PATCH or DELETE (a part declared with handles: on those methods) and the law names each mutating action that reaches no policy."
@@ -0,0 +1,21 @@
1
+ recipe: background-work
2
+ roles:
3
+ - name: tasks
4
+ match: ["app/tasks/**"]
5
+ - name: task-classes
6
+ match: ["app/tasks/**"]
7
+ kind: symbol
8
+ where: { symbol_kind: "class" }
9
+ - name: controllers
10
+ match: ["app/controllers/**"]
11
+ rules:
12
+ - id: maintenance-tasks-live-in-their-namespace
13
+ require_name: task-classes
14
+ pattern: "Maintenance::*"
15
+ because: "The task runner resolves task constants from that namespace, which is the one its generator creates, so a task declared outside it never appears in the runner and cannot be run by anyone."
16
+
17
+ - id: maintenance-tasks-never-reach-controllers
18
+ forbid: tasks
19
+ to: controllers
20
+ via: calls
21
+ because: "A task runs from the runner or the console with no request around it, so controller code reached from one has nothing to read."
@@ -0,0 +1,13 @@
1
+ recipe: data-ownership
2
+ roles:
3
+ - name: store
4
+ match: ["app/models/**"]
5
+ - name: readers
6
+ optional: true
7
+ rules:
8
+ - id: readers-read-the-store-only
9
+ allow: readers
10
+ only: [store]
11
+ via: calls
12
+ mode: advisory
13
+ because: "A reader derives its answer from the store alone, which is what keeps it replayable against a snapshot; a reader that reaches a service or a client answers differently on a different machine. Bind your query objects or report classes as the readers."
@@ -0,0 +1,12 @@
1
+ recipe: tenant-foreign-key
2
+ roles:
3
+ - name: tables
4
+ match: ["db/schema.rb", "db/structure.sql", "app/models/**"]
5
+ kind: storage
6
+ rules:
7
+ - id: tenant-foreign-key
8
+ require: tables
9
+ when_prop_contains: { prop: columns, value: TENANT_COLUMN }
10
+ must_prop_contain: { prop: fk_constraints, value: "TENANT_COLUMN->TENANT_TABLE" }
11
+ mode: advisory
12
+ because: "A table carrying the tenant column references the tenant, so the database should say so with a foreign key; a row that outlives its tenant is the leak nobody notices until an export. Advisory while a backfill path that ships the column before the constraint is reconciled with the rule; the verdict names every table that carries the column without the key."
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: enola-guides
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Muhamed Isabegovic
@@ -115,8 +115,12 @@ files:
115
115
  - guides/12-the-wrapper.md
116
116
  - lib/enola-guides.rb
117
117
  - recipes/README.md
118
+ - recipes/api-boundaries.yaml
119
+ - recipes/background-work.yaml
120
+ - recipes/data-ownership.yaml
118
121
  - recipes/ember-conventions.yaml
119
122
  - recipes/house-recipe-template.yaml
123
+ - recipes/tenant-foreign-key.yaml
120
124
  - templates/constraints-starter/background-jobs-never-invoke-controller-code.rb
121
125
  - templates/constraints-starter/models-do-not-reach-controllers.rb
122
126
  - templates/constraints-starter/policies-only-answer.rb