change_requests 0.2.0 → 0.2.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/README.md +5 -0
- data/docs/adr/0001-headless-domain-core.md +40 -0
- data/docs/adr/0002-mountable-engine-with-isolated-namespace.md +38 -0
- data/docs/adr/0003-actor-references-as-triples.md +43 -0
- data/docs/adr/0004-uuid-primary-keys.md +35 -0
- data/docs/adr/0005-string-states-with-check-constraints.md +43 -0
- data/docs/adr/0006-creation-time-immutability.md +39 -0
- data/docs/adr/0007-append-only-audit-trail.md +42 -0
- data/docs/adr/0008-staged-multi-quorum-schema.md +43 -0
- data/docs/adr/0009-host-owned-schema.md +43 -0
- data/docs/adr/0010-operations-must-be-declared.md +57 -0
- data/docs/adr/0011-system-sentinel-actor.md +36 -0
- data/docs/adr/0012-declared-error-taxonomy.md +40 -0
- data/docs/adr/0013-json-runtime-pin.md +37 -0
- data/docs/adr/0014-executable-architecture-rules.md +45 -0
- data/docs/adr/README.md +25 -0
- data/lib/change_requests/authorization/callable.rb +30 -0
- data/lib/change_requests/authorization/permissions.rb +66 -1
- data/lib/change_requests/commands/approve.rb +69 -0
- data/lib/change_requests/commands/base.rb +113 -0
- data/lib/change_requests/commands/cancel.rb +43 -0
- data/lib/change_requests/commands/comment.rb +40 -0
- data/lib/change_requests/commands/create.rb +169 -0
- data/lib/change_requests/commands/expire.rb +27 -0
- data/lib/change_requests/commands/reject.rb +77 -0
- data/lib/change_requests/commands/unapprove.rb +49 -0
- data/lib/change_requests/configuration.rb +22 -1
- data/lib/change_requests/errors.rb +45 -26
- data/lib/change_requests/guards/approve.rb +31 -0
- data/lib/change_requests/guards/base.rb +201 -0
- data/lib/change_requests/guards/cancel.rb +32 -0
- data/lib/change_requests/guards/comment.rb +32 -0
- data/lib/change_requests/guards/execute.rb +48 -0
- data/lib/change_requests/guards/expire.rb +37 -0
- data/lib/change_requests/guards/reject.rb +38 -0
- data/lib/change_requests/guards/unapprove.rb +41 -0
- data/lib/change_requests/models/approval.rb +1 -1
- data/lib/change_requests/models/attempt.rb +3 -1
- data/lib/change_requests/models/concerns/actor_columns.rb +12 -6
- data/lib/change_requests/models/event.rb +3 -1
- data/lib/change_requests/models/quorum.rb +7 -2
- data/lib/change_requests/models/request.rb +11 -2
- data/lib/change_requests/models/stage.rb +10 -3
- data/lib/change_requests/operation.rb +6 -2
- data/lib/change_requests/operations.rb +14 -4
- data/lib/change_requests/translation.rb +3 -1
- data/lib/change_requests/version.rb +1 -1
- data/lib/change_requests/workflow.rb +3 -1
- data/lib/change_requests.rb +9 -3
- data/lib/generators/change_requests/install/templates/migration.rb.tt +5 -0
- metadata +33 -1
|
@@ -23,8 +23,11 @@ module ChangeRequests
|
|
|
23
23
|
class_methods do
|
|
24
24
|
# `label: false` for a reference that names who *may* act rather than who did - the
|
|
25
25
|
# eligibility rows carry no snapshot, because nothing has happened yet to snapshot.
|
|
26
|
+
#
|
|
27
|
+
# `identity: true` also snapshots `config.actor_identity`, which is what lets the gem tell
|
|
28
|
+
# that Admin#7 and User#99 are one human. Null unless the host configured it (§9.4).
|
|
26
29
|
def actor_reference(prefix, optional: false, allow_system: false, registry: :actor_types,
|
|
27
|
-
label: true)
|
|
30
|
+
label: true, identity: false)
|
|
28
31
|
declare_type_validation(prefix, optional: optional, allow_system: allow_system,
|
|
29
32
|
registry: registry)
|
|
30
33
|
|
|
@@ -33,7 +36,7 @@ module ChangeRequests
|
|
|
33
36
|
validates :"#{prefix}_label", presence: true if label
|
|
34
37
|
end
|
|
35
38
|
|
|
36
|
-
define_actor_accessors(prefix, registry: registry, label: label)
|
|
39
|
+
define_actor_accessors(prefix, registry: registry, label: label, identity: identity)
|
|
37
40
|
end
|
|
38
41
|
|
|
39
42
|
# QuorumPermission carries a type and nothing else: NULL there means "any registered class".
|
|
@@ -57,7 +60,7 @@ module ChangeRequests
|
|
|
57
60
|
allow_nil: true
|
|
58
61
|
end
|
|
59
62
|
|
|
60
|
-
def define_actor_accessors(prefix, registry:, label:)
|
|
63
|
+
def define_actor_accessors(prefix, registry:, label:, identity:)
|
|
61
64
|
generated = Module.new do
|
|
62
65
|
# No `*_id=` override: the column is a string, so ActiveRecord already casts on
|
|
63
66
|
# assignment. A User with a uuid key and an Admin with a bigint key share it (§5.7).
|
|
@@ -66,10 +69,11 @@ module ChangeRequests
|
|
|
66
69
|
|
|
67
70
|
return nil if type.blank?
|
|
68
71
|
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
reference = { type: type, id: public_send(:"#{prefix}_id") }
|
|
73
|
+
reference[:label] = public_send(:"#{prefix}_label") if label
|
|
74
|
+
reference[:identity] = public_send(:"#{prefix}_identity") if identity
|
|
71
75
|
|
|
72
|
-
|
|
76
|
+
reference
|
|
73
77
|
end
|
|
74
78
|
|
|
75
79
|
define_method(:"#{prefix}=") do |actor|
|
|
@@ -82,6 +86,8 @@ module ChangeRequests
|
|
|
82
86
|
public_send(:"#{prefix}_type=", attributes[:type])
|
|
83
87
|
public_send(:"#{prefix}_id=", attributes[:id])
|
|
84
88
|
public_send(:"#{prefix}_label=", attributes[:label]) if label
|
|
89
|
+
public_send(:"#{prefix}_identity=", ChangeRequests.config.actor_identity&.call(actor)) \
|
|
90
|
+
if identity && actor
|
|
85
91
|
end
|
|
86
92
|
end
|
|
87
93
|
|
|
@@ -41,6 +41,8 @@ module ChangeRequests
|
|
|
41
41
|
|
|
42
42
|
# Expiry, the reaper and undeclared-operation cancellation have no actor. The sentinel keeps the
|
|
43
43
|
# triple not-null so no presenter or export branches on nil.
|
|
44
|
-
def system_actor?
|
|
44
|
+
def system_actor?
|
|
45
|
+
actor_type == SYSTEM_ACTOR[:type]
|
|
46
|
+
end
|
|
45
47
|
end
|
|
46
48
|
end
|
|
@@ -47,8 +47,13 @@ module ChangeRequests
|
|
|
47
47
|
validates :name, format: { with: NAME_FORMAT }, allow_nil: true
|
|
48
48
|
validates :name, uniqueness: { scope: :change_request_stage_id }, allow_nil: true
|
|
49
49
|
|
|
50
|
-
def any_match?
|
|
51
|
-
|
|
50
|
+
def any_match?
|
|
51
|
+
permission_match == "any"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def all_match?
|
|
55
|
+
permission_match == "all"
|
|
56
|
+
end
|
|
52
57
|
|
|
53
58
|
# A nameless quorum has no display text of its own; the stage's label is the honest answer.
|
|
54
59
|
def label
|
|
@@ -23,7 +23,7 @@ module ChangeRequests
|
|
|
23
23
|
# so the row stays readable once the actor and the payload's records are gone.
|
|
24
24
|
readonly_after_create :operation_key, :operation_version, :service, :method_name,
|
|
25
25
|
:payload, :payload_labels,
|
|
26
|
-
:requester_type, :requester_id, :requester_label,
|
|
26
|
+
:requester_type, :requester_id, :requester_label, :requester_identity,
|
|
27
27
|
:tenant_type, :tenant_id,
|
|
28
28
|
:max_attempts
|
|
29
29
|
|
|
@@ -37,7 +37,7 @@ module ChangeRequests
|
|
|
37
37
|
has_many :attempts, -> { order(:number) },
|
|
38
38
|
class_name: "ChangeRequests::Attempt", inverse_of: :change_request
|
|
39
39
|
|
|
40
|
-
actor_reference :requester
|
|
40
|
+
actor_reference :requester, identity: true
|
|
41
41
|
actor_reference :executer, optional: true
|
|
42
42
|
actor_reference :tenant, optional: true, registry: :tenant_types
|
|
43
43
|
|
|
@@ -56,5 +56,14 @@ module ChangeRequests
|
|
|
56
56
|
def current_stage
|
|
57
57
|
stages.find_by(position: current_stage_position)
|
|
58
58
|
end
|
|
59
|
+
|
|
60
|
+
# The attempts rows *are* the count; there is no counter column (§19.12). `max_attempts` is
|
|
61
|
+
# readonly after create and `>= 1` by CHECK, so there is no zero case to defend against.
|
|
62
|
+
#
|
|
63
|
+
# It answers "is there an attempt left", not "may this be executed" - Guards::Execute combines
|
|
64
|
+
# it with the status, and M3a's Commands::Execute and M3b's reaper both read it.
|
|
65
|
+
def retryable?
|
|
66
|
+
attempts.count < max_attempts
|
|
67
|
+
end
|
|
59
68
|
end
|
|
60
69
|
end
|
|
@@ -35,11 +35,18 @@ module ChangeRequests
|
|
|
35
35
|
format: { with: NAME_FORMAT },
|
|
36
36
|
uniqueness: { scope: :change_request_id }
|
|
37
37
|
|
|
38
|
-
def any_quorum?
|
|
39
|
-
|
|
38
|
+
def any_quorum?
|
|
39
|
+
satisfied_by == "any_quorum"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def all_quorums?
|
|
43
|
+
satisfied_by == "all_quorums"
|
|
44
|
+
end
|
|
40
45
|
|
|
41
46
|
# Still accepting decisions. A closed stage is immutable and a rejected one stopped the request.
|
|
42
|
-
def open?
|
|
47
|
+
def open?
|
|
48
|
+
pending? || satisfied?
|
|
49
|
+
end
|
|
43
50
|
|
|
44
51
|
# Display text, resolved separately from the identifier (§5.9).
|
|
45
52
|
def label
|
|
@@ -25,11 +25,15 @@ module ChangeRequests
|
|
|
25
25
|
@workflow = Workflow.new
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
def method_name
|
|
28
|
+
def method_name
|
|
29
|
+
@method_name&.to_sym
|
|
30
|
+
end
|
|
29
31
|
|
|
30
32
|
# The two defaults below resolve lazily: initializer order is the host's, and an operations file
|
|
31
33
|
# that loads before the configuration file should still see the host's defaults.
|
|
32
|
-
def max_attempts
|
|
34
|
+
def max_attempts
|
|
35
|
+
@max_attempts || ChangeRequests.config.default_max_attempts
|
|
36
|
+
end
|
|
33
37
|
|
|
34
38
|
def expires_in
|
|
35
39
|
return @expires_in if @expires_in_set
|
|
@@ -25,11 +25,21 @@ module ChangeRequests
|
|
|
25
25
|
|
|
26
26
|
# nil, not a raise: every guard asks whether the declaration is still live, and §5.11 makes
|
|
27
27
|
# that a refusal the guard words itself.
|
|
28
|
-
def [](key)
|
|
28
|
+
def [](key)
|
|
29
|
+
@operations[key.to_s]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def keys
|
|
33
|
+
@operations.keys
|
|
34
|
+
end
|
|
29
35
|
|
|
30
|
-
def
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
def each(&)
|
|
37
|
+
@operations.each_value(&)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def clear
|
|
41
|
+
@operations.clear
|
|
42
|
+
end
|
|
33
43
|
|
|
34
44
|
private
|
|
35
45
|
|
data/lib/change_requests.rb
CHANGED
|
@@ -19,13 +19,19 @@ module ChangeRequests
|
|
|
19
19
|
# `unless mod.respond_to?(:table_name_prefix)`, and its version yields `change_requests_stages`.
|
|
20
20
|
# `Request` is the one model that overrides its table name; it would derive
|
|
21
21
|
# `change_request_requests`.
|
|
22
|
-
def table_name_prefix
|
|
22
|
+
def table_name_prefix
|
|
23
|
+
"change_request_"
|
|
24
|
+
end
|
|
23
25
|
|
|
24
|
-
def config
|
|
26
|
+
def config
|
|
27
|
+
@config ||= Configuration.new
|
|
28
|
+
end
|
|
25
29
|
|
|
26
30
|
# The declaration registry (§6.12). Memoised for the same reason as config: initializers
|
|
27
31
|
# accumulate into it.
|
|
28
|
-
def operations
|
|
32
|
+
def operations
|
|
33
|
+
@operations ||= Operations.new
|
|
34
|
+
end
|
|
29
35
|
|
|
30
36
|
# The (type, id, label) triple for a host record, with the label snapshotted through the
|
|
31
37
|
# registered lambda. Raises before anything is constantized: the registry is the allowlist, and
|
|
@@ -21,6 +21,7 @@ class CreateChangeRequests < ActiveRecord::Migration[<%= migration_version %>]
|
|
|
21
21
|
t.string :requester_type, null: false
|
|
22
22
|
t.string :requester_id, null: false
|
|
23
23
|
t.string :requester_label, null: false
|
|
24
|
+
t.string :requester_identity
|
|
24
25
|
|
|
25
26
|
t.string :executer_type
|
|
26
27
|
t.string :executer_id
|
|
@@ -65,6 +66,10 @@ class CreateChangeRequests < ActiveRecord::Migration[<%= migration_version %>]
|
|
|
65
66
|
t.string :status, null: false, default: "pending"
|
|
66
67
|
|
|
67
68
|
t.datetime :satisfied_at, precision: 6
|
|
69
|
+
# The rejection's counterpart to satisfied_at: when the stage was stopped, and therefore when
|
|
70
|
+
# op.cooldown stops being able to give it back. Written from M9b; here from the first
|
|
71
|
+
# migration so no host needs a second one for it (PLAN §5.2, §7.1).
|
|
72
|
+
t.datetime :rejected_at, precision: 6
|
|
68
73
|
t.datetime :closed_at, precision: 6
|
|
69
74
|
|
|
70
75
|
t.timestamps precision: 6, null: false
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: change_requests
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andreas Finger
|
|
@@ -310,8 +310,32 @@ files:
|
|
|
310
310
|
- Rakefile
|
|
311
311
|
- config/routes.rb
|
|
312
312
|
- docs/08_events_and_notifications.md
|
|
313
|
+
- docs/adr/0001-headless-domain-core.md
|
|
314
|
+
- docs/adr/0002-mountable-engine-with-isolated-namespace.md
|
|
315
|
+
- docs/adr/0003-actor-references-as-triples.md
|
|
316
|
+
- docs/adr/0004-uuid-primary-keys.md
|
|
317
|
+
- docs/adr/0005-string-states-with-check-constraints.md
|
|
318
|
+
- docs/adr/0006-creation-time-immutability.md
|
|
319
|
+
- docs/adr/0007-append-only-audit-trail.md
|
|
320
|
+
- docs/adr/0008-staged-multi-quorum-schema.md
|
|
321
|
+
- docs/adr/0009-host-owned-schema.md
|
|
322
|
+
- docs/adr/0010-operations-must-be-declared.md
|
|
323
|
+
- docs/adr/0011-system-sentinel-actor.md
|
|
324
|
+
- docs/adr/0012-declared-error-taxonomy.md
|
|
325
|
+
- docs/adr/0013-json-runtime-pin.md
|
|
326
|
+
- docs/adr/0014-executable-architecture-rules.md
|
|
327
|
+
- docs/adr/README.md
|
|
313
328
|
- lib/change_requests.rb
|
|
329
|
+
- lib/change_requests/authorization/callable.rb
|
|
314
330
|
- lib/change_requests/authorization/permissions.rb
|
|
331
|
+
- lib/change_requests/commands/approve.rb
|
|
332
|
+
- lib/change_requests/commands/base.rb
|
|
333
|
+
- lib/change_requests/commands/cancel.rb
|
|
334
|
+
- lib/change_requests/commands/comment.rb
|
|
335
|
+
- lib/change_requests/commands/create.rb
|
|
336
|
+
- lib/change_requests/commands/expire.rb
|
|
337
|
+
- lib/change_requests/commands/reject.rb
|
|
338
|
+
- lib/change_requests/commands/unapprove.rb
|
|
315
339
|
- lib/change_requests/configuration.rb
|
|
316
340
|
- lib/change_requests/configuration/actor_type.rb
|
|
317
341
|
- lib/change_requests/configuration/registered_type.rb
|
|
@@ -319,6 +343,14 @@ files:
|
|
|
319
343
|
- lib/change_requests/engine.rb
|
|
320
344
|
- lib/change_requests/errors.rb
|
|
321
345
|
- lib/change_requests/guards/.keep
|
|
346
|
+
- lib/change_requests/guards/approve.rb
|
|
347
|
+
- lib/change_requests/guards/base.rb
|
|
348
|
+
- lib/change_requests/guards/cancel.rb
|
|
349
|
+
- lib/change_requests/guards/comment.rb
|
|
350
|
+
- lib/change_requests/guards/execute.rb
|
|
351
|
+
- lib/change_requests/guards/expire.rb
|
|
352
|
+
- lib/change_requests/guards/reject.rb
|
|
353
|
+
- lib/change_requests/guards/unapprove.rb
|
|
322
354
|
- lib/change_requests/models/.keep
|
|
323
355
|
- lib/change_requests/models/approval.rb
|
|
324
356
|
- lib/change_requests/models/approval_quorum.rb
|