hub_kernel 0.15.0 → 0.17.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 +4 -4
- data/README.md +18 -1
- data/lib/hub_kernel/exposes.rb +22 -3
- data/lib/hub_kernel/hubs.rb +18 -1
- data/lib/hub_kernel/version.rb +1 -1
- data/the_local/agents/hub_kernel-develop.md +28 -4
- data/the_local/agents/hub_kernel-info.md +6 -5
- data/the_local/agents/hub_kernel-install.md +19 -11
- data/the_local/interface.yml +2 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: daa7466e09530b359428c0801ec176dc77a91a4002b3f37426bec62206a0d0a1
|
|
4
|
+
data.tar.gz: fa4a074a0511159376b00b5eed7a0e025e75cced2f90deb108c9d9b28da2775a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 219629da71a783d2dde7824af4655e57b20a23b6a2911c046e202a894a7beee56271fd290ee84f20be81baa74ec5ed3c928bef0bd2899c009b8a81d8979f1a98
|
|
7
|
+
data.tar.gz: fb3eb3f6f64110207466adc325a433b88c1b12a037052a5a26db0cc3dc8d09082d9da3a772a89ba405b72b263ae386889b39df86ddd955f360a215f5e653797d
|
data/README.md
CHANGED
|
@@ -68,7 +68,7 @@ app left unfilled raises `HubKernel::UnwiredPortError`, such as "Supplies' spend
|
|
|
68
68
|
recorder is not wired".
|
|
69
69
|
|
|
70
70
|
### The start check
|
|
71
|
-
Every hub that declares a port is on `HubKernel::Hubs.list`. Call
|
|
71
|
+
Every hub that declares a port or exposes a method is on `HubKernel::Hubs.list`. Call
|
|
72
72
|
`HubKernel::Hubs.check!` after the app fills its ports, and the app refuses to start
|
|
73
73
|
while any port is unfilled:
|
|
74
74
|
|
|
@@ -83,6 +83,10 @@ It raises `HubKernel::UnwiredPortError` naming every unfilled port with its hub,
|
|
|
83
83
|
line. Running it inside `to_prepare` checks again after every code reload, and a hub
|
|
84
84
|
declared again on a reload stays on the list once.
|
|
85
85
|
|
|
86
|
+
While any hub exposes a method, it also names the permission check or the account scope
|
|
87
|
+
when either is unset or set to something that cannot be called. An app whose hubs expose
|
|
88
|
+
nothing starts without either.
|
|
89
|
+
|
|
86
90
|
### The hub check
|
|
87
91
|
A hub's own test can check that the app filled every one of its ports, so a missing
|
|
88
92
|
fill shows in the suite rather than on the next start:
|
|
@@ -130,6 +134,19 @@ method requires, and passes on only the values the method is listed with. A hub
|
|
|
130
134
|
`HubKernel::Refused` with a reason when it will not do what was asked, and the reason
|
|
131
135
|
reaches the caller unchanged.
|
|
132
136
|
|
|
137
|
+
### Listing what a person may call
|
|
138
|
+
An interface can show a caller what it may call before it calls anything. `exposures`
|
|
139
|
+
gives every entry a hub exposes, each with its name, the values it takes and whether it
|
|
140
|
+
writes. `exposures_for` gives only the entries the host's permission check allows one
|
|
141
|
+
person on one account:
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
Supplies.exposures_for(person: current_person, account: current_account).map(&:name)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
It refuses a missing person or account as a call by name does, and a hub that exposes
|
|
148
|
+
nothing, or a person allowed nothing, gets an empty list.
|
|
149
|
+
|
|
133
150
|
### The host's permission check and account scope
|
|
134
151
|
Every call by name asks the host whether the person may take the action on the account,
|
|
135
152
|
and runs the method inside the host's scope for that account. The host sets both once
|
data/lib/hub_kernel/exposes.rb
CHANGED
|
@@ -2,6 +2,7 @@ require "hub_kernel/action"
|
|
|
2
2
|
require "hub_kernel/authz"
|
|
3
3
|
require "hub_kernel/context"
|
|
4
4
|
require "hub_kernel/ports"
|
|
5
|
+
require "hub_kernel/hubs"
|
|
5
6
|
|
|
6
7
|
module HubKernel
|
|
7
8
|
class UnexposedMethodError < StandardError; end
|
|
@@ -11,14 +12,22 @@ module HubKernel
|
|
|
11
12
|
Exposed = Data.define(:name, :takes, :writes)
|
|
12
13
|
|
|
13
14
|
def exposes(name, takes:, writes:)
|
|
15
|
+
Hubs.add(self)
|
|
14
16
|
exposed_methods[name.to_s] = Exposed.new(name: name, takes: takes, writes: writes)
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def exposed(name) = exposed_methods[name.to_s]
|
|
18
20
|
|
|
21
|
+
def exposures = exposed_methods.values
|
|
22
|
+
|
|
23
|
+
def exposures_for(person:, account:)
|
|
24
|
+
refuse_without_caller(person, account)
|
|
25
|
+
|
|
26
|
+
exposures.select { |exposure| allowed?(exposure, person, account) }
|
|
27
|
+
end
|
|
28
|
+
|
|
19
29
|
def call_exposed(name, values:, person:, account:)
|
|
20
|
-
|
|
21
|
-
raise MissingArgumentError, "A call by name needs an account" if account.nil?
|
|
30
|
+
refuse_without_caller(person, account)
|
|
22
31
|
|
|
23
32
|
exposure = exposed(name) || raise(UnexposedMethodError, "#{exposing_hub} does not expose #{name}")
|
|
24
33
|
refuse_unless_allowed(exposure, person, account)
|
|
@@ -36,12 +45,22 @@ module HubKernel
|
|
|
36
45
|
|
|
37
46
|
def exposing_hub = name.demodulize
|
|
38
47
|
|
|
48
|
+
def refuse_without_caller(person, account)
|
|
49
|
+
raise MissingArgumentError, "A call by name needs a person" if person.nil?
|
|
50
|
+
raise MissingArgumentError, "A call by name needs an account" if account.nil?
|
|
51
|
+
end
|
|
52
|
+
|
|
39
53
|
def refuse_unless_allowed(exposure, person, account)
|
|
54
|
+
raise NotAllowed, "#{exposing_hub} #{exposure.name}" unless allowed?(exposure, person, account)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def allowed?(exposure, person, account)
|
|
40
58
|
raise UnwiredPortError, "hub_kernel's permission check is not filled" unless Authz.check
|
|
41
59
|
|
|
42
60
|
answer = Authz.check.call(person, "#{exposing_hub.underscore}:#{exposure.name}", account)
|
|
43
61
|
raise NonBooleanAnswerError, "The permission check must answer true or false, got #{answer.inspect}" unless [ true, false ].include?(answer)
|
|
44
|
-
|
|
62
|
+
|
|
63
|
+
answer
|
|
45
64
|
end
|
|
46
65
|
|
|
47
66
|
def within_account(account, &call)
|
data/lib/hub_kernel/hubs.rb
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
require "hub_kernel/authz"
|
|
2
|
+
require "hub_kernel/context"
|
|
3
|
+
|
|
1
4
|
module HubKernel
|
|
2
5
|
module Hubs
|
|
3
6
|
def self.add(hub)
|
|
@@ -8,8 +11,22 @@ module HubKernel
|
|
|
8
11
|
def self.list = @list ||= []
|
|
9
12
|
|
|
10
13
|
def self.check!
|
|
11
|
-
unwired = list.flat_map(&:unwired_ports)
|
|
14
|
+
unwired = list.select { |hub| hub.respond_to?(:unwired_ports) }.flat_map(&:unwired_ports) + unfilled_host_answers
|
|
12
15
|
raise UnwiredPortError, unwired.join("\n") if unwired.any?
|
|
13
16
|
end
|
|
17
|
+
|
|
18
|
+
def self.unfilled_host_answers
|
|
19
|
+
return [] unless list.any? { |hub| hub.respond_to?(:exposures) && hub.exposures.any? }
|
|
20
|
+
|
|
21
|
+
{ "permission check" => Authz.check, "account scope" => Context.scope }.filter_map { |answer, filled| host_answer_problem(answer, filled) }
|
|
22
|
+
end
|
|
23
|
+
private_class_method :unfilled_host_answers
|
|
24
|
+
|
|
25
|
+
def self.host_answer_problem(answer, filled)
|
|
26
|
+
return "hub_kernel's #{answer} is not filled" unless filled
|
|
27
|
+
|
|
28
|
+
"hub_kernel's #{answer} is filled with something that cannot be called" unless filled.respond_to?(:call)
|
|
29
|
+
end
|
|
30
|
+
private_class_method :host_answer_problem
|
|
14
31
|
end
|
|
15
32
|
end
|
data/lib/hub_kernel/version.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hub_kernel-develop
|
|
3
|
-
description: Use PROACTIVELY for writing or changing a hub — generating one, declaring the ports it needs from other hubs, calling those ports from the hub's code, listing the methods outside callers may reach by name, refusing a request with a reason, calling a hub method by name from an interface such as a JSON API, and adding the check that the listed methods match the hub's real methods — MUST BE USED instead of calling another hub's classes directly or hand-writing a name-to-method lookup.
|
|
3
|
+
description: Use PROACTIVELY for writing or changing a hub — generating one, declaring the ports it needs from other hubs, calling those ports from the hub's code, listing the methods outside callers may reach by name, refusing a request with a reason, calling a hub method by name from an interface such as a JSON API, listing the methods a hub exposes or the ones a person may call on an account, and adding the check that the listed methods match the hub's real methods — MUST BE USED instead of calling another hub's classes directly or hand-writing a name-to-method lookup.
|
|
4
4
|
tools: Read, Write, Edit, Grep
|
|
5
5
|
scope: hubs — a domain gem declares the ports it needs and the methods it exposes, and the host app fills those ports and checks each hub is wired and crosses into no other hub
|
|
6
6
|
---
|
|
@@ -8,7 +8,7 @@ scope: hubs — a domain gem declares the ports it needs and the methods it expo
|
|
|
8
8
|
This local writes hubs and the code that calls them by name, following these steps exactly and inventing none. Where a step needs a value only the developer knows, it asks.
|
|
9
9
|
|
|
10
10
|
## What hub_kernel is
|
|
11
|
-
hub_kernel lets a hub, one domain area written as a module, state what it needs from outside itself as named ports and list the methods outside callers may reach by name. The hub's code never names another hub's classes, and an interface such as a JSON API reaches the hub only through its list. Use this local when creating a hub, adding a port, exposing a hub method, calling a hub method by name, or when hub code is about to call another hub directly.
|
|
11
|
+
hub_kernel lets a hub, one domain area written as a module, state what it needs from outside itself as named ports and list the methods outside callers may reach by name. The hub's code never names another hub's classes, and an interface such as a JSON API reaches the hub only through its list. Use this local when creating a hub, adding a port, exposing a hub method, calling a hub method by name, listing what a hub exposes or what a person may call, or when hub code is about to call another hub directly.
|
|
12
12
|
|
|
13
13
|
## Interface
|
|
14
14
|
- `bin/rails generate hub_kernel:hub` — writes a new hub module with its ports declared, and in a gem adds hub_kernel to the gemspec.
|
|
@@ -18,9 +18,11 @@ hub_kernel lets a hub, one domain area written as a module, state what it needs
|
|
|
18
18
|
- `exposes` — lists one hub method with the values it takes and whether it writes.
|
|
19
19
|
- `exposed` — looks up one listed method by name and answers its entry, or `nil` when it is not listed.
|
|
20
20
|
- `call_exposed` — calls a listed method by name for a person and an account, after the host's permission check, inside the host's account scope.
|
|
21
|
+
- `exposures` — answers every listed method's entry, in the order the hub lists them.
|
|
22
|
+
- `exposures_for` — answers the entries the host's permission check allows a person on an account, without calling any method.
|
|
21
23
|
- `HubKernel::Refused` — the error a hub method raises when it will not do what was asked, and its message reaches the caller unchanged.
|
|
22
24
|
- `HubKernel::UnexposedMethodError` — raised by `call_exposed` for a name the hub does not list.
|
|
23
|
-
- `HubKernel::MissingArgumentError` — raised by `call_exposed` for a call with no person
|
|
25
|
+
- `HubKernel::MissingArgumentError` — raised by `call_exposed` and `exposures_for` for a call with no person or no account, and by `call_exposed` for a value the method requires left out.
|
|
24
26
|
- `HubKernel::NotAllowed` — raised by `call_exposed` when the host's permission check answers `false`, before the method runs.
|
|
25
27
|
- `HubKernel::NonBooleanAnswerError` — raised by `call_exposed` when the host's permission check answers anything but `true` or `false`.
|
|
26
28
|
- `HubKernel::Conformance::Exposed` — a test module that fails naming each listed method the hub has no method for, and each one listed with values it does not take.
|
|
@@ -146,7 +148,28 @@ hub_kernel lets a hub, one domain area written as a module, state what it needs
|
|
|
146
148
|
|
|
147
149
|
11. Ask the developer how the interface answers each of those failures, such as which HTTP status each one returns. Do not pick the responses yourself. Show a `HubKernel::Refused` message to the caller as it is.
|
|
148
150
|
|
|
149
|
-
|
|
151
|
+
### List what a hub exposes
|
|
152
|
+
12. Where the interface shows callers which methods they can reach, such as a list of tools or actions, read it from the hub rather than writing it out again:
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
Supplies.exposures
|
|
156
|
+
# => every entry, with .name, .takes, .writes, in the order the hub lists them
|
|
157
|
+
|
|
158
|
+
Supplies.exposures_for(person: current_person, account: current_account)
|
|
159
|
+
# => only the entries the host's permission check allows this person on this account
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
- Each entry is the same one `exposed` answers.
|
|
163
|
+
- A hub that lists nothing answers `[]` from both, and a person allowed nothing answers `[]` from `exposures_for`.
|
|
164
|
+
- `exposures_for` asks the permission check once per entry with the same action name `call_exposed` uses, such as `"supplies:record_purchase"`, and runs no hub method.
|
|
165
|
+
- `exposures_for` fails in this order, and each failure stops it:
|
|
166
|
+
1. `HubKernel::MissingArgumentError` when `person:` or `account:` is `nil`.
|
|
167
|
+
2. An error naming the host's permission check when the host has not set it and the hub lists at least one method.
|
|
168
|
+
3. `HubKernel::NonBooleanAnswerError` when the permission check answers anything but `true` or `false`.
|
|
169
|
+
- Pass the same `person:` and `account:` the interface passes to `call_exposed`.
|
|
170
|
+
- Ask the developer whether the interface shows every exposed method or only those the person may call. Do not pick yourself.
|
|
171
|
+
|
|
172
|
+
13. Run the test suite and confirm the conventions below hold.
|
|
150
173
|
|
|
151
174
|
## Conventions
|
|
152
175
|
- A hub's code reaches another hub only through a port, never by naming its classes.
|
|
@@ -154,4 +177,5 @@ hub_kernel lets a hub, one domain area written as a module, state what it needs
|
|
|
154
177
|
- Every exposed method is listed with `exposes`, and the exposed-list check passes.
|
|
155
178
|
- When an exposed method gains, loses or renames a keyword, update its `takes:` in the same change.
|
|
156
179
|
- When a hub gains a port, tell the developer every host app must fill it, since calling it unfilled raises an error and the host's start check fails.
|
|
180
|
+
- When a hub exposes its first method, tell the developer every host app must set the permission check and the account scope, since the host's start check fails while any hub exposes a method and either is unset or cannot be called.
|
|
157
181
|
- Adding the gem to a host, filling ports, running the start check, setting the permission check and the account scope, and the host's hub and crossing checks belong to the install local and are out of scope here.
|
|
@@ -11,30 +11,31 @@ This local explains hub_kernel and the words it uses. It makes no changes and gi
|
|
|
11
11
|
|
|
12
12
|
hub_kernel splits a Rails app into hubs, where each hub is one area of the domain, such as supplies or finance, usually shipped in its own domain gem. A hub states what it needs from outside itself as named ports, and the host app decides what fills each one, so a hub never names another hub's code directly.
|
|
13
13
|
|
|
14
|
-
A hub also lists the methods outside callers may reach by name, with the values each takes and whether it writes. An interface such as a JSON API calls through that list, and every call is checked against the host's permission rule and run inside the host's account scope. Reach for hub_kernel when an app has several domain areas that should talk to each other only through declared entry points, and you want the app to refuse to start, or the suite to fail, when that is not true.
|
|
14
|
+
A hub also lists the methods outside callers may reach by name, with the values each takes and whether it writes. An interface such as a JSON API calls through that list, and every call is checked against the host's permission rule and run inside the host's account scope. The same list can be read back whole, or cut down to only the methods one person is allowed to call on one account, so an interface can show a person what they may do before they try it. Reach for hub_kernel when an app has several domain areas that should talk to each other only through declared entry points, and you want the app to refuse to start, or the suite to fail, when that is not true.
|
|
15
15
|
|
|
16
16
|
## Interface
|
|
17
17
|
|
|
18
18
|
This local documents no commands. The surface belongs to the other two locals:
|
|
19
19
|
|
|
20
20
|
- **hub_kernel-install** owns everything the host app does: adding the gem, filling each hub's ports, running the start check, setting the permission rule and the account scope, and adding the hub check and the crossing check to the host's tests.
|
|
21
|
-
- **hub_kernel-develop** owns everything a hub's author does: generating a hub, declaring its ports, listing the methods callers may reach by name, refusing a request, and adding the check that the listed methods match the hub's real methods.
|
|
21
|
+
- **hub_kernel-develop** owns everything a hub's author does: generating a hub, declaring its ports, listing the methods callers may reach by name, reading that list back for a person and an account, refusing a request, and adding the check that the listed methods match the hub's real methods.
|
|
22
22
|
|
|
23
23
|
## How to use it
|
|
24
24
|
|
|
25
25
|
Decide which side you are on:
|
|
26
26
|
|
|
27
|
-
- You are wiring hubs into an app, or the app will not start because something is not wired: use **hub_kernel-install**.
|
|
27
|
+
- You are wiring hubs into an app, or the app will not start because something is not wired or not filled: use **hub_kernel-install**.
|
|
28
28
|
- You are writing or changing a hub, inside a domain gem or the app: use **hub_kernel-develop**.
|
|
29
29
|
|
|
30
30
|
## Conventions
|
|
31
31
|
|
|
32
|
-
- **Hub** — one domain area, written as a module. In a domain gem it sits under the gem's namespace, such as Billing::Ledger.
|
|
32
|
+
- **Hub** — one domain area, written as a module. In a domain gem it sits under the gem's namespace, such as Billing::Ledger. A hub is known to the app once it declares a port or lists a method callable by name.
|
|
33
33
|
- **Ports** — what a hub needs from outside. Each has a name and the method the hub's own code calls, such as a spend recorder called as record_spend.
|
|
34
34
|
- **Filling ports** — the host app sets each of a hub's ports to any callable, usually another hub's method, in its setup that runs again on every code reload. Ports are "wired" once they are filled.
|
|
35
|
-
- **Start check** — the app refuses to start while any hub's ports are not wired, and names each one, such as "Supplies' spend recorder is not wired".
|
|
35
|
+
- **Start check** — the app refuses to start while any hub's ports are not wired, and names each one, such as "Supplies' spend recorder is not wired". While any hub lists a method callable by name, it also refuses to start until the permission check and the account scope are both set to something that can be called, and names whichever is missing or cannot be called. An app whose hubs list no such methods starts without either.
|
|
36
36
|
- **Hub check** — a host test that fails naming each of a hub's ports left unfilled or filled with something that cannot be called. A domain gem never runs it, since the gem never fills its own ports.
|
|
37
37
|
- **Call by name** — a caller reaches a hub method by its name as a string, with the values sent, the person, and the account. Unknown names, a missing person or account, and missing required values are refused.
|
|
38
|
+
- **What a person may call** — the hub's list of methods callable by name, kept to those the permission check allows for one person on one account. Asking with no person or no account is refused, the same as a call by name. A hub that lists nothing, or a person allowed nothing, gets an empty list rather than an error.
|
|
38
39
|
- **Action** — the name a permission is asked for, made of the hub and the method, such as supplies:record_purchase.
|
|
39
40
|
- **Permission check** — one host-wide rule answering true or false for a person, an action, and an account. Any other answer is an error.
|
|
40
41
|
- **Account scope** — one host-wide wrapper that runs each call by name inside the account it was made for.
|
|
@@ -8,13 +8,13 @@ scope: hubs — a domain gem declares the ports it needs and the methods it expo
|
|
|
8
8
|
This local follows these steps exactly and invents none. Where a step needs a value only the developer knows, it asks.
|
|
9
9
|
|
|
10
10
|
## What hub_kernel is
|
|
11
|
-
hub_kernel lets a Rails host app fill the ports each hub declares, refuse to start while a port is unfilled, answer permission and account scope for calls made by name, and fail its suite when one hub's code names a class another hub owns. Hook it in when the app uses hubs, its own or ones shipped in domain gems.
|
|
11
|
+
hub_kernel lets a Rails host app fill the ports each hub declares, refuse to start while a port is unfilled, answer permission and account scope for calls made by name, answer permission when an interface lists what a person may call, and fail its suite when one hub's code names a class another hub owns. Hook it in when the app uses hubs, its own or ones shipped in domain gems.
|
|
12
12
|
|
|
13
13
|
## Interface
|
|
14
14
|
- `gem "hub_kernel"` — the Gemfile line that adds the gem to the host app.
|
|
15
|
-
- `HubKernel::Hubs.check!` — raises `HubKernel::UnwiredPortError` naming every unfilled port of every loaded hub, one per line, such as "Supplies' spend recorder is not wired".
|
|
16
|
-
- `HubKernel::Hubs.list` — the hubs that have loaded and declared at least one port.
|
|
17
|
-
- `HubKernel::UnwiredPortError` — raised by the start check, by calling an unfilled port,
|
|
15
|
+
- `HubKernel::Hubs.check!` — raises `HubKernel::UnwiredPortError` naming every unfilled port of every loaded hub, one per line, such as "Supplies' spend recorder is not wired", and, while any loaded hub exposes a method, the permission check or the account scope when either is unset or cannot be called.
|
|
16
|
+
- `HubKernel::Hubs.list` — the hubs that have loaded and declared at least one port or exposed at least one method.
|
|
17
|
+
- `HubKernel::UnwiredPortError` — raised by the start check, by calling an unfilled port, by a call by name while the permission check or the account scope is unset, and by listing what a person may call from a hub that exposes at least one method while the permission check is unset.
|
|
18
18
|
- `HubKernel::Authz.check` — the host-wide permission rule, a callable taking a person, an action and an account and answering `true` or `false`.
|
|
19
19
|
- `HubKernel::Context.scope` — the host-wide account scope, a callable taking an account and a block and running the block inside that account.
|
|
20
20
|
- `HubKernel::Conformance::Hub` — a test module that fails naming each of one hub's ports left unfilled or filled with something that cannot be called.
|
|
@@ -42,17 +42,24 @@ hub_kernel lets a Rails host app fill the ports each hub declares, refuse to sta
|
|
|
42
42
|
|
|
43
43
|
A port accepts any object that responds to `call`. The start check only sees hubs that have loaded, so name every hub in this block. Run `HubKernel::Hubs.list` in `bin/rails console` to see which hubs it saw.
|
|
44
44
|
|
|
45
|
-
4.
|
|
45
|
+
4. Run `bin/rails runner 'HubKernel::Hubs.check!'`. If the error names "hub_kernel's permission check" or "hub_kernel's account scope", a loaded hub exposes a method and step 5 is required. If it names neither, skip to step 6.
|
|
46
46
|
|
|
47
|
-
5. Ask the developer for the app's permission rule and the account scope, since neither has a default. Set both
|
|
47
|
+
5. Ask the developer for the app's permission rule and the account scope, since neither has a default. Set both in the same `to_prepare` block, before the start check:
|
|
48
48
|
|
|
49
49
|
```ruby
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
Rails.application.config.to_prepare do
|
|
51
|
+
Supplies.spend_recorder = Finance.method(:record_spend)
|
|
52
|
+
HubKernel::Authz.check = ->(person, action, account) { Permissions.allow?(person, action, account) }
|
|
53
|
+
HubKernel::Context.scope = ->(account, &call) { Current.set(account: account, &call) }
|
|
54
|
+
HubKernel::Hubs.check!
|
|
55
|
+
end
|
|
52
56
|
```
|
|
53
57
|
|
|
58
|
+
- While any loaded hub exposes a method, the start check refuses to start until both are set to something that responds to `call`.
|
|
59
|
+
- The permission check answers both a call by name and a listing of what a person may call, so the two always agree.
|
|
60
|
+
- Listing what a person may call needs only the permission check, while a call by name needs both.
|
|
54
61
|
- The action is a string made of the hub and the method, such as `"supplies:record_purchase"`.
|
|
55
|
-
- The permission check must answer exactly `true` or `false`, and any other answer makes the call raise an error.
|
|
62
|
+
- The permission check must answer exactly `true` or `false`, and any other answer makes the call or the listing raise an error.
|
|
56
63
|
- The account scope must take the block and run it, or the hub method never runs.
|
|
57
64
|
|
|
58
65
|
6. Add one hub check per hub to the host's tests, such as `test/hubs/supplies_hub_test.rb`:
|
|
@@ -107,12 +114,13 @@ hub_kernel lets a Rails host app fill the ports each hub declares, refuse to sta
|
|
|
107
114
|
|
|
108
115
|
## Conventions
|
|
109
116
|
- After installing, `bin/rails runner 'HubKernel::Hubs.check!'` exits with no error, and every hub check and the crossing check pass.
|
|
110
|
-
- The start check reports unfilled ports
|
|
117
|
+
- The start check reports unfilled ports, and the permission check or account scope when unset or not callable while a hub exposes a method.
|
|
118
|
+
- The hub check reports one hub's unfilled ports and each port filled with something that cannot be called, which the start check does not report.
|
|
111
119
|
- A domain gem never runs the hub check, since it never fills its own ports, so the hub check always lives in the host's tests.
|
|
112
120
|
- A view belongs to the hub that owns its controller, then to the hub that owns the record its folder is named after, and otherwise to the host's layer.
|
|
113
121
|
- A constant inside another hub's class counts as naming that class.
|
|
114
122
|
- Only the host's layer may name a hub's interface module.
|
|
115
123
|
- A class listed by name under `owners` belongs to that hub even inside another hub's namespace.
|
|
116
124
|
- The crossing check fails with "The crossing check found no files to read" when its `files:` pattern matches nothing the map owns, so fix the pattern or the map rather than the test.
|
|
117
|
-
- When a hub gains a port, fill it in the `to_prepare` block. When a class is added, renamed or removed, update the crossing map, since the check fails on a class no hub owns and on a mapped class that no longer exists.
|
|
125
|
+
- When a hub gains a port, fill it in the `to_prepare` block. When a hub first exposes a method, set the permission check and the account scope there too. When a class is added, renamed or removed, update the crossing map, since the check fails on a class no hub owns and on a mapped class that no longer exists.
|
|
118
126
|
- Writing a hub, declaring its ports, and listing the methods it exposes are not part of installing and are out of scope here.
|
data/the_local/interface.yml
CHANGED