enola-guides 0.1.1 → 0.1.2
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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +1 -1
- data/examples/laws/cluster-laws/README.md +1 -1
- data/examples/laws/cluster-laws/enola/constraints/a-mutating-action-is-authorized.rb +10 -0
- data/examples/laws/cluster-laws/enola/constraints/every-api-route-has-a-consumer.rb +9 -0
- data/examples/laws/cluster-laws/enola/constraints/one-owner-per-table.rb +9 -0
- data/examples/laws/maintenance-tasks/README.md +1 -1
- data/examples/laws/maintenance-tasks/enola/constraints/a-maintenance-task-never-invokes-controller-code.rb +9 -0
- data/examples/laws/maintenance-tasks/enola/constraints/{maintenance-tasks.rb → every-maintenance-task-lives-in-the-maintenance-namespace.rb} +0 -7
- data/examples/rails-shop/enola/constraints/a-concern-never-reaches-the-class-that-includes-it.rb +9 -0
- data/examples/rails-shop/enola/constraints/a-mutating-action-is-authorized.rb +10 -0
- data/examples/rails-shop/enola/constraints/a-service-answers-to-call.rb +9 -0
- data/examples/rails-shop/enola/constraints/background-jobs-never-invoke-controller-code.rb +9 -0
- data/examples/rails-shop/enola/constraints/billing-is-documented.rb +10 -0
- data/examples/rails-shop/enola/constraints/billing-keeps-to-its-own-tables.rb +9 -0
- data/examples/rails-shop/enola/constraints/frames-keep-their-query-budget.rb +9 -0
- data/examples/rails-shop/enola/constraints/jobs-models-and-mailers-never-depend-on-each-other-in-a-circle.rb +9 -0
- data/examples/rails-shop/enola/constraints/orders-are-reached-through-their-public-surface.rb +9 -0
- data/examples/rails-shop/enola/constraints/reach-for-an-existing-service.rb +8 -0
- data/examples/rails-shop/enola/constraints/scopes-come-in-pairs.rb +9 -0
- data/examples/rails-shop/enola/constraints/the-public-api-stays-small.rb +10 -0
- data/examples/rails-shop/enola/constraints/the-request-api-stays-in-controllers.rb +9 -0
- data/lib/enola-guides.rb +1 -1
- data/templates/constraints-starter/background-jobs-never-invoke-controller-code.rb +9 -0
- data/templates/constraints-starter/models-do-not-reach-controllers.rb +9 -0
- data/templates/constraints-starter/policies-only-answer.rb +9 -0
- data/templates/constraints-starter/the-request-api-stays-in-controllers.rb +9 -0
- metadata +23 -5
- data/examples/laws/cluster-laws/enola/constraints/cluster.rb +0 -24
- data/examples/rails-shop/enola/constraints/laws.rb +0 -93
- data/templates/constraints-starter.rb +0 -30
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: df9b7b5b2b3fc6589f9353c4f2198b08283fe80fafd2dacda91dea2d66c5a2fb
|
|
4
|
+
data.tar.gz: 20172bf3167db5fb3d690d401f3313d38861d10f86f7010607c4ccae98353b68
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f705dc88218a61a94d6261b624e6744d8fd14212e6524b062f2f9f9520751013968f4a208382861516265ac4ca41db11ca483e09acf52792120aa704f01251a0
|
|
7
|
+
data.tar.gz: 65b0c4c5cd94ca30b85d3528737489c83c0ff73b8a7b3402dd2d39c45742af8ca679cfaef2ec04ea8837e3ac5d92a1881960b2a7429df198e758ef7b7d8c8435
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.2 (2026-08-22)
|
|
4
|
+
|
|
5
|
+
One law per file: the Rails shop, the cluster laws, the maintenance-task laws
|
|
6
|
+
and the starter each put every law in a file of its own that declares the parts it
|
|
7
|
+
speaks about, so a reader finds a law by its name and a reviewer sees one
|
|
8
|
+
law per diff.
|
|
9
|
+
|
|
3
10
|
## 0.1.1 (2026-08-22)
|
|
4
11
|
|
|
5
12
|
The laws catalogue: `examples/laws/`, twenty-six complete declarations one
|
data/README.md
CHANGED
|
@@ -18,7 +18,7 @@ gem contents enola-guides
|
|
|
18
18
|
## Start with an example
|
|
19
19
|
|
|
20
20
|
[`examples/rails-shop`](examples/rails-shop): a Rails application declaring
|
|
21
|
-
thirteen laws in the Ruby DSL, from "jobs never invoke controller code" to
|
|
21
|
+
thirteen laws in the Ruby DSL, one file each, from "jobs never invoke controller code" to
|
|
22
22
|
"billing keeps to its own tables" and "a mutating action is authorized",
|
|
23
23
|
with Prism and Rubydex configured as providers beside the extractors.
|
|
24
24
|
[`examples/ember-app`](examples/ember-app): an Ember application bound to
|
|
@@ -5,5 +5,5 @@ repositories, mutating actions selected through the routes they handle, and
|
|
|
5
5
|
routes no loaded client calls. Each refuses by name in a single-repository
|
|
6
6
|
snapshot rather than reporting silence as compliance.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
One file per law under `enola/constraints/`, each file declaring the parts it speaks about. Copy it, run `enola constraints lint .`,
|
|
9
9
|
then `enola --generate . && enola check .`.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Enola.architecture "platform" do
|
|
2
|
+
part :mutating_actions, service: :backend, files: "app/controllers/**", handles: [:post, :put, :patch, :delete]
|
|
3
|
+
part :policies, service: :backend, files: "app/policies/**"
|
|
4
|
+
|
|
5
|
+
law "a mutating action is authorized" do
|
|
6
|
+
mutating_actions.must_reach :policies
|
|
7
|
+
why "an action behind a POST, PUT, PATCH or DELETE route changes state, and the policy call is the one place the change is authorized"
|
|
8
|
+
mode :advisory
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Enola.architecture "platform" do
|
|
2
|
+
part :tables, files: "app/models/**", kind: :storage
|
|
3
|
+
|
|
4
|
+
law "one owner per table" do
|
|
5
|
+
tables.must_be_unique_across by: :table
|
|
6
|
+
why "a table two repositories both model is written by two codebases that never see each other's validations or callbacks"
|
|
7
|
+
mode :advisory
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -5,5 +5,5 @@ depends on, and a boundary against controller code that has no request to
|
|
|
5
5
|
read. Both were mined from a codebase where they already held, which is the
|
|
6
6
|
right moment to declare a law.
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
One file per law under `enola/constraints/`, each file declaring the parts it speaks about. Copy it, run `enola constraints lint .`,
|
|
9
9
|
then `enola --generate . && enola check .`.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Enola.architecture "app" do
|
|
2
|
+
part :tasks, files: "app/tasks/**"
|
|
3
|
+
part :controllers, files: "app/controllers/**"
|
|
4
|
+
|
|
5
|
+
law "a maintenance task never invokes controller code" do
|
|
6
|
+
tasks.must_not_call controllers
|
|
7
|
+
why "a task runs from the runner or the console with no request around it, so controller code reached from one has nothing to read"
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
Enola.architecture "app" do
|
|
2
2
|
part :maintenance_tasks, files: "app/tasks/**", kind: :symbol, where: { symbol_kind: "class" }
|
|
3
|
-
part :tasks, files: "app/tasks/**"
|
|
4
|
-
part :controllers, files: "app/controllers/**"
|
|
5
3
|
|
|
6
4
|
law "every maintenance task lives in the Maintenance namespace" do
|
|
7
5
|
maintenance_tasks.names_must_match "Maintenance::*"
|
|
8
6
|
why "the task runner resolves task constants from that namespace, so a task declared outside it never appears in the runner and cannot be run by anyone"
|
|
9
7
|
end
|
|
10
|
-
|
|
11
|
-
law "a maintenance task never invokes controller code" do
|
|
12
|
-
tasks.must_not_call controllers
|
|
13
|
-
why "a task runs from the runner or the console with no request around it, so controller code reached from one has nothing to read"
|
|
14
|
-
end
|
|
15
8
|
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Enola.architecture "shop" do
|
|
2
|
+
rails
|
|
3
|
+
part :mutating_actions, files: "app/controllers/**", handles: [:post, :put, :patch, :delete]
|
|
4
|
+
|
|
5
|
+
law "a mutating action is authorized" do
|
|
6
|
+
mutating_actions.must_reach :policies
|
|
7
|
+
why "every write passes a policy before it touches a record"
|
|
8
|
+
mode :ratchet
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Enola.architecture "shop" do
|
|
2
|
+
part :service_objects, files: "app/services/**", kind: :symbol, where: { symbol_kind: "class" }, owns: :methods
|
|
3
|
+
|
|
4
|
+
law "a service answers to call" do
|
|
5
|
+
service_objects.must_define :call
|
|
6
|
+
why "one door per service, so a caller never reads it to find the verb"
|
|
7
|
+
mode :ratchet
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Enola.architecture "shop" do
|
|
2
|
+
part :billing, files: ["app/billing/**", "app/models/invoice.rb"]
|
|
3
|
+
|
|
4
|
+
law "billing is documented" do
|
|
5
|
+
billing.must_be_governed
|
|
6
|
+
since "2026-09-01"
|
|
7
|
+
why "code under a decision needs the page that decided it"
|
|
8
|
+
mode :ratchet
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Enola.architecture "shop" do
|
|
2
|
+
part :billing, files: ["app/billing/**", "app/models/invoice.rb"]
|
|
3
|
+
|
|
4
|
+
law "billing keeps to its own tables" do
|
|
5
|
+
billing.storage_must_stay_home
|
|
6
|
+
why "a part that writes another part's table owns a bug it cannot see"
|
|
7
|
+
mode :ratchet
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Enola.architecture "shop" do
|
|
2
|
+
rails
|
|
3
|
+
|
|
4
|
+
law "frames keep their query budget" do
|
|
5
|
+
controllers.must_keep_budget metric: :queries, max: 20
|
|
6
|
+
why "a frame past twenty queries is a page that will not scale; refused by name until a capture exists"
|
|
7
|
+
mode :advisory
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Enola.architecture "shop" do
|
|
2
|
+
part :orders, files: ["app/orders/**", "app/models/order.rb"], public: ["app/orders/public/**"]
|
|
3
|
+
|
|
4
|
+
law "orders are reached through their public surface" do
|
|
5
|
+
orders.stays_inside
|
|
6
|
+
why "the public directory is the contract; everything else may change"
|
|
7
|
+
mode :ratchet
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Enola.architecture "shop" do
|
|
2
|
+
part :service_objects, files: "app/services/**", kind: :symbol, where: { symbol_kind: "class" }, owns: :methods
|
|
3
|
+
|
|
4
|
+
law "reach for an existing service" do
|
|
5
|
+
service_objects.advises "look in app/services before writing a new one"
|
|
6
|
+
why "duplication is a judgement no fact answers; advice where the edit happens is what can be offered honestly"
|
|
7
|
+
end
|
|
8
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Enola.architecture "shop" do
|
|
2
|
+
part :records, ancestor: "ApplicationRecord"
|
|
3
|
+
|
|
4
|
+
law "scopes come in pairs" do
|
|
5
|
+
records.names_must_match "with_*", requires: "without_*"
|
|
6
|
+
why "every with_ scope has a without_ sibling, or the query reads half a thought"
|
|
7
|
+
mode :advisory
|
|
8
|
+
end
|
|
9
|
+
end
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Enola.architecture "shop" do
|
|
2
|
+
rails
|
|
3
|
+
|
|
4
|
+
law "the request api stays in controllers" do
|
|
5
|
+
models.must_not_call "params", receiver: :none
|
|
6
|
+
why "params without a receiver is the controller's; a model reading it only works inside a request"
|
|
7
|
+
mode :ratchet
|
|
8
|
+
end
|
|
9
|
+
end
|
data/lib/enola-guides.rb
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Enola.architecture "app" do
|
|
2
|
+
rails
|
|
3
|
+
|
|
4
|
+
law "the request api stays in controllers" do
|
|
5
|
+
models.must_not_call "params", receiver: :none
|
|
6
|
+
why "params without a receiver is the controller's; a model reading it only works inside a request"
|
|
7
|
+
mode :ratchet
|
|
8
|
+
end
|
|
9
|
+
end
|
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.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Muhamed Isabegovic
|
|
@@ -29,7 +29,9 @@ files:
|
|
|
29
29
|
- examples/laws/clean-rings/README.md
|
|
30
30
|
- examples/laws/clean-rings/enola/constraints/clean.yaml
|
|
31
31
|
- examples/laws/cluster-laws/README.md
|
|
32
|
-
- examples/laws/cluster-laws/enola/constraints/
|
|
32
|
+
- examples/laws/cluster-laws/enola/constraints/a-mutating-action-is-authorized.rb
|
|
33
|
+
- examples/laws/cluster-laws/enola/constraints/every-api-route-has-a-consumer.rb
|
|
34
|
+
- examples/laws/cluster-laws/enola/constraints/one-owner-per-table.rb
|
|
33
35
|
- examples/laws/cqrs-queries-never-write/README.md
|
|
34
36
|
- examples/laws/cqrs-queries-never-write/enola/constraints/cqrs.yaml
|
|
35
37
|
- examples/laws/foreign-keys/README.md
|
|
@@ -39,7 +41,8 @@ files:
|
|
|
39
41
|
- examples/laws/instantiation/README.md
|
|
40
42
|
- examples/laws/instantiation/enola/constraints/ceremony.yaml
|
|
41
43
|
- examples/laws/maintenance-tasks/README.md
|
|
42
|
-
- examples/laws/maintenance-tasks/enola/constraints/maintenance-
|
|
44
|
+
- examples/laws/maintenance-tasks/enola/constraints/a-maintenance-task-never-invokes-controller-code.rb
|
|
45
|
+
- examples/laws/maintenance-tasks/enola/constraints/every-maintenance-task-lives-in-the-maintenance-namespace.rb
|
|
43
46
|
- examples/laws/mixins-stay-independent/README.md
|
|
44
47
|
- examples/laws/mixins-stay-independent/enola/constraints/concerns.yaml
|
|
45
48
|
- examples/laws/naming-conventions/README.md
|
|
@@ -79,7 +82,19 @@ files:
|
|
|
79
82
|
- examples/laws/vanilla-rails-stays-empty/README.md
|
|
80
83
|
- examples/laws/vanilla-rails-stays-empty/enola/constraints/vanilla.yaml
|
|
81
84
|
- examples/rails-shop/enola.yaml
|
|
82
|
-
- examples/rails-shop/enola/constraints/
|
|
85
|
+
- examples/rails-shop/enola/constraints/a-concern-never-reaches-the-class-that-includes-it.rb
|
|
86
|
+
- examples/rails-shop/enola/constraints/a-mutating-action-is-authorized.rb
|
|
87
|
+
- examples/rails-shop/enola/constraints/a-service-answers-to-call.rb
|
|
88
|
+
- examples/rails-shop/enola/constraints/background-jobs-never-invoke-controller-code.rb
|
|
89
|
+
- examples/rails-shop/enola/constraints/billing-is-documented.rb
|
|
90
|
+
- examples/rails-shop/enola/constraints/billing-keeps-to-its-own-tables.rb
|
|
91
|
+
- examples/rails-shop/enola/constraints/frames-keep-their-query-budget.rb
|
|
92
|
+
- examples/rails-shop/enola/constraints/jobs-models-and-mailers-never-depend-on-each-other-in-a-circle.rb
|
|
93
|
+
- examples/rails-shop/enola/constraints/orders-are-reached-through-their-public-surface.rb
|
|
94
|
+
- examples/rails-shop/enola/constraints/reach-for-an-existing-service.rb
|
|
95
|
+
- examples/rails-shop/enola/constraints/scopes-come-in-pairs.rb
|
|
96
|
+
- examples/rails-shop/enola/constraints/the-public-api-stays-small.rb
|
|
97
|
+
- examples/rails-shop/enola/constraints/the-request-api-stays-in-controllers.rb
|
|
83
98
|
- guides/01-start-here.md
|
|
84
99
|
- guides/02-rails.md
|
|
85
100
|
- guides/03-ember.md
|
|
@@ -92,7 +107,10 @@ files:
|
|
|
92
107
|
- recipes/README.md
|
|
93
108
|
- recipes/ember-conventions.yaml
|
|
94
109
|
- recipes/house-recipe-template.yaml
|
|
95
|
-
- templates/constraints-starter.rb
|
|
110
|
+
- templates/constraints-starter/background-jobs-never-invoke-controller-code.rb
|
|
111
|
+
- templates/constraints-starter/models-do-not-reach-controllers.rb
|
|
112
|
+
- templates/constraints-starter/policies-only-answer.rb
|
|
113
|
+
- templates/constraints-starter/the-request-api-stays-in-controllers.rb
|
|
96
114
|
- templates/enola-check.sh
|
|
97
115
|
- templates/enola-cluster.yaml
|
|
98
116
|
- templates/enola-findings.rb
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
Enola.architecture "platform" do
|
|
2
|
-
part :tables, files: "app/models/**", kind: :storage
|
|
3
|
-
part :mutating_actions, service: :backend, files: "app/controllers/**", handles: [:post, :put, :patch, :delete]
|
|
4
|
-
part :policies, service: :backend, files: "app/policies/**"
|
|
5
|
-
part :api, service: :backend, files: "config/**", kind: :route
|
|
6
|
-
|
|
7
|
-
law "one owner per table" do
|
|
8
|
-
tables.must_be_unique_across by: :table
|
|
9
|
-
why "a table two repositories both model is written by two codebases that never see each other's validations or callbacks"
|
|
10
|
-
mode :advisory
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
law "a mutating action is authorized" do
|
|
14
|
-
mutating_actions.must_reach :policies
|
|
15
|
-
why "an action behind a POST, PUT, PATCH or DELETE route changes state, and the policy call is the one place the change is authorized"
|
|
16
|
-
mode :advisory
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
law "every api route has a consumer" do
|
|
20
|
-
api.must_have_consumer
|
|
21
|
-
why "a route nobody calls is a surface nobody maintains"
|
|
22
|
-
mode :advisory
|
|
23
|
-
end
|
|
24
|
-
end
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# The Ruby DSL, end to end, for a Rails shop. Parsed by enola, never
|
|
2
|
-
# executed. Every sentence below compiles to a rule form the YAML also
|
|
3
|
-
# accepts; `enola constraints lint .` shows what each part resolved to.
|
|
4
|
-
Enola.architecture "shop" do
|
|
5
|
-
rails
|
|
6
|
-
|
|
7
|
-
part :billing, files: ["app/billing/**", "app/models/invoice.rb"]
|
|
8
|
-
part :orders, files: ["app/orders/**", "app/models/order.rb"], public: ["app/orders/public/**"]
|
|
9
|
-
part :records, ancestor: "ApplicationRecord"
|
|
10
|
-
part :mutating_actions, files: "app/controllers/**", handles: [:post, :put, :patch, :delete]
|
|
11
|
-
part :service_objects, files: "app/services/**", kind: :symbol, where: { symbol_kind: "class" }, owns: :methods
|
|
12
|
-
part :api, files: "config/**", kind: :route
|
|
13
|
-
part :tables, files: "app/models/**", kind: :storage
|
|
14
|
-
|
|
15
|
-
law "background jobs never invoke controller code" do
|
|
16
|
-
jobs.must_not_call controllers
|
|
17
|
-
why "rendering from a job goes through ApplicationController.renderer"
|
|
18
|
-
mode :ratchet
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
law "the request api stays in controllers" do
|
|
22
|
-
models.must_not_call "params", receiver: :none
|
|
23
|
-
why "params without a receiver is the controller's; a model reading it only works inside a request"
|
|
24
|
-
mode :ratchet
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
law "billing keeps to its own tables" do
|
|
28
|
-
billing.storage_must_stay_home
|
|
29
|
-
why "a part that writes another part's table owns a bug it cannot see"
|
|
30
|
-
mode :ratchet
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
law "orders are reached through their public surface" do
|
|
34
|
-
orders.stays_inside
|
|
35
|
-
why "the public directory is the contract; everything else may change"
|
|
36
|
-
mode :ratchet
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
law "a mutating action is authorized" do
|
|
40
|
-
mutating_actions.must_reach :policies
|
|
41
|
-
why "every write passes a policy before it touches a record"
|
|
42
|
-
mode :ratchet
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
law "a concern never reaches the class that includes it" do
|
|
46
|
-
concerns.must_not_reach_includers
|
|
47
|
-
why "a mixin that knows its includer is half a class in hiding"
|
|
48
|
-
mode :ratchet
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
law "jobs, models and mailers never depend on each other in a circle" do
|
|
52
|
-
jobs.must_not_cycle_with :models, :mailers
|
|
53
|
-
why "parts that reach each other in a circle cannot be taken apart"
|
|
54
|
-
mode :advisory
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
law "a service answers to call" do
|
|
58
|
-
service_objects.must_define :call
|
|
59
|
-
why "one door per service, so a caller never reads it to find the verb"
|
|
60
|
-
mode :ratchet
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
law "scopes come in pairs" do
|
|
64
|
-
records.names_must_match "with_*", requires: "without_*"
|
|
65
|
-
why "every with_ scope has a without_ sibling, or the query reads half a thought"
|
|
66
|
-
mode :advisory
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
law "the public api stays small" do
|
|
70
|
-
api.at_most 120
|
|
71
|
-
growth 5
|
|
72
|
-
why "a route is a promise; the count may grow, but only on purpose"
|
|
73
|
-
mode :ratchet
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
law "frames keep their query budget" do
|
|
77
|
-
controllers.must_keep_budget metric: :queries, max: 20
|
|
78
|
-
why "a frame past twenty queries is a page that will not scale; refused by name until a capture exists"
|
|
79
|
-
mode :advisory
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
law "billing is documented" do
|
|
83
|
-
billing.must_be_governed
|
|
84
|
-
since "2026-09-01"
|
|
85
|
-
why "code under a decision needs the page that decided it"
|
|
86
|
-
mode :ratchet
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
law "reach for an existing service" do
|
|
90
|
-
service_objects.advises "look in app/services before writing a new one"
|
|
91
|
-
why "duplication is a judgement no fact answers; advice where the edit happens is what can be offered honestly"
|
|
92
|
-
end
|
|
93
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
# A first declaration for a Rails application. Copy to enola/constraints/
|
|
2
|
-
# and keep the laws you agree with; every law carries its reason, and
|
|
3
|
-
# ratchet mode fails only what a change adds.
|
|
4
|
-
Enola.architecture "app" do
|
|
5
|
-
rails
|
|
6
|
-
|
|
7
|
-
law "background jobs never invoke controller code" do
|
|
8
|
-
jobs.must_not_call controllers
|
|
9
|
-
why "rendering from a job goes through ApplicationController.renderer"
|
|
10
|
-
mode :ratchet
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
law "models do not reach controllers" do
|
|
14
|
-
models.must_not_call controllers
|
|
15
|
-
why "a model that knows the request cannot be used off the request"
|
|
16
|
-
mode :ratchet
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
law "policies only answer" do
|
|
20
|
-
policies.must_not_call jobs
|
|
21
|
-
why "authorization is asked many times per request and sometimes speculatively"
|
|
22
|
-
mode :ratchet
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
law "the request api stays in controllers" do
|
|
26
|
-
models.must_not_call "params", receiver: :none
|
|
27
|
-
why "params without a receiver is the controller's; a model reading it only works inside a request"
|
|
28
|
-
mode :ratchet
|
|
29
|
-
end
|
|
30
|
-
end
|