agreements 0.1.0 → 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 +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +122 -28
- data/app/models/agreements/acceptance.rb +1 -1
- data/app/models/agreements/version.rb +9 -3
- data/lib/agreements/enforcement.rb +6 -3
- data/lib/agreements/engine.rb +2 -0
- data/lib/agreements/recorder.rb +6 -4
- data/lib/agreements/version.rb +1 -1
- data/lib/agreements.rb +10 -4
- data/lib/generators/agreements/install/templates/create_agreements_tables.rb.tt +2 -0
- metadata +61 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 81d911005203680cbf4a2c85a8da0c4bec60c04c9094e3e35af19c8e4fb595d7
|
|
4
|
+
data.tar.gz: 67a34497cc8a10c5f2803745e28b45949eb0ef550aa951b57f08f588c1c809cb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6f78483f20c53fdb31f4c0537c96016d216efb6b362ea35bdb8090752c6585a4ad76f19885cd81f051f55955d355bb58510edb0031266eef37bc32e89c45ed84
|
|
7
|
+
data.tar.gz: 6c64d27234a8d20e0bdece9b695ec67e3272da6df194abbe4f894490831f9659361e0891a3a70a38a2ed375e3caf773bacd13586fbc358381e53df39213ada35
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 - 2026-11-12
|
|
4
|
+
|
|
5
|
+
- Answer pending non-HTML requests with `403 Forbidden` instead of letting them
|
|
6
|
+
through. `require_agreement` returns `:forbidden` for them.
|
|
7
|
+
- Reject `/\host` return paths, using Rails' `url_from`.
|
|
8
|
+
- Accept an optional `key` on document references for translated link titles.
|
|
9
|
+
- Depend on Action Pack, Active Record, Railties and Global ID instead of all
|
|
10
|
+
of Rails.
|
|
11
|
+
- Document the safe data-migration pattern, Global ID app pinning and a `nil`
|
|
12
|
+
`current_version` on `VersionNotCurrent`.
|
|
13
|
+
|
|
14
|
+
## 0.2.0 - 2026-08-27
|
|
15
|
+
|
|
16
|
+
- Record the exact localized acceptance statement and locale shown by the host.
|
|
17
|
+
- Publish releases through RubyGems trusted publishing.
|
|
18
|
+
|
|
3
19
|
## 0.1.0 - 2026-08-25
|
|
4
20
|
|
|
5
21
|
- Extract the agreement-version and acceptance primitives proven in EthicsPortal.
|
data/README.md
CHANGED
|
@@ -4,8 +4,8 @@ Auditable acceptance of externally hosted legal agreements for Rails.
|
|
|
4
4
|
|
|
5
5
|
`agreements` answers one durable question:
|
|
6
6
|
|
|
7
|
-
> Which agreement version did this subject accept,
|
|
8
|
-
> acceptance, under what authority, and when?
|
|
7
|
+
> Which agreement version and localized statement did this subject accept,
|
|
8
|
+
> who performed the acceptance, under what authority, and when?
|
|
9
9
|
|
|
10
10
|
It deliberately does not host legal documents or provide a legal CMS. Your
|
|
11
11
|
legal or marketing site remains authoritative; the gem stores immutable
|
|
@@ -29,31 +29,72 @@ view, initializer, JavaScript, CSS, or admin dashboard to integrate.
|
|
|
29
29
|
|
|
30
30
|
Ruby >= 3.2 · Rails >= 7.1 and < 9 · PostgreSQL and SQLite
|
|
31
31
|
|
|
32
|
+
### Upgrading from 0.2
|
|
33
|
+
|
|
34
|
+
Version 0.3 has no schema change. `require_agreement` now answers a pending
|
|
35
|
+
non-HTML request with `403 Forbidden` instead of letting it through. If your
|
|
36
|
+
API already checks `pending_version` before calling it, nothing changes.
|
|
37
|
+
|
|
38
|
+
### Upgrading from 0.1
|
|
39
|
+
|
|
40
|
+
Version 0.2 adds non-null `acceptance_statement` and `locale` columns to
|
|
41
|
+
`agreements_acceptances`. Add them in a host migration before upgrading. For
|
|
42
|
+
existing evidence, copy each version's canonical statement and use an honest
|
|
43
|
+
locale such as `und` when the displayed locale is unknown.
|
|
44
|
+
|
|
32
45
|
## Define a version
|
|
33
46
|
|
|
34
|
-
Agreement versions are deployed data.
|
|
35
|
-
the same
|
|
47
|
+
Agreement versions are deployed data. Insert them in a data migration, and keep
|
|
48
|
+
the same list in your seeds so fresh databases match production:
|
|
36
49
|
|
|
37
50
|
```ruby
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
51
|
+
class AddUserTerms20260816 < ActiveRecord::Migration[8.1]
|
|
52
|
+
class Version < ActiveRecord::Base
|
|
53
|
+
self.table_name = "agreements_versions"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
VERSION = {
|
|
57
|
+
agreement_key: "user_terms",
|
|
58
|
+
version: "2026-08-16",
|
|
59
|
+
acceptance_statement: "I accept the Terms of Service and acknowledge the Privacy Notice.",
|
|
60
|
+
documents: [
|
|
61
|
+
{ key: "terms_of_service", title: "Terms of Service", url: "https://example.com/terms-2026-08-16" },
|
|
62
|
+
{ key: "privacy_notice", title: "Privacy Notice", url: "https://example.com/privacy-2026-08-16" }
|
|
63
|
+
]
|
|
64
|
+
}.freeze
|
|
65
|
+
|
|
66
|
+
def up
|
|
67
|
+
version = Version.find_or_create_by!(VERSION.slice(:agreement_key, :version)) do |record|
|
|
68
|
+
record.assign_attributes(VERSION)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
unless version.acceptance_statement == VERSION[:acceptance_statement] &&
|
|
72
|
+
version.documents == VERSION[:documents].as_json
|
|
73
|
+
raise "#{version.agreement_key} #{version.version} already exists with different content"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def down
|
|
78
|
+
raise ActiveRecord::IrreversibleMigration, "Agreement versions are evidence"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
47
81
|
```
|
|
48
82
|
|
|
83
|
+
The migration defines its own model so it keeps working when the gem's model
|
|
84
|
+
changes later. The final check stops a deploy if a version label already exists
|
|
85
|
+
with different content: a deployed version must never change. Use the same
|
|
86
|
+
create-then-check loop in your seeds.
|
|
87
|
+
|
|
49
88
|
The newest inserted row for an agreement key is current immediately. There is
|
|
50
89
|
no draft, publish, activate, or scheduling lifecycle. A correction or legal
|
|
51
90
|
update is a new immutable row with a new version label.
|
|
52
91
|
|
|
53
|
-
Document references accept `title`, an HTTPS `url`,
|
|
54
|
-
`sha256
|
|
55
|
-
|
|
56
|
-
|
|
92
|
+
Document references accept `title`, an HTTPS `url`, an optional 64-character
|
|
93
|
+
`sha256`, and an optional `key`. Use `key` (lowercase letters, digits and
|
|
94
|
+
underscores) to look up a translated link title, so your views do not depend on
|
|
95
|
+
the English `title`. The gem validates a supplied digest but does not fetch
|
|
96
|
+
remote pages, calculate hashes, or retain document bytes. Hashing and archival
|
|
97
|
+
belong in the trusted legal-document publishing process.
|
|
57
98
|
|
|
58
99
|
## Ask what is pending
|
|
59
100
|
|
|
@@ -66,6 +107,19 @@ Subjects and actors may be records responding to `to_global_id`, or explicit
|
|
|
66
107
|
non-empty opaque strings. The gem stores only the resulting keys; it does not
|
|
67
108
|
own authentication, tenancy, roles, or authorization.
|
|
68
109
|
|
|
110
|
+
A Global ID contains your application name and the model class, for example
|
|
111
|
+
`gid://my-app/User/1`. If either changes, old acceptances no longer match and
|
|
112
|
+
every subject is asked to accept again. Pin the application name now so a
|
|
113
|
+
module rename cannot change it:
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
# config/application.rb
|
|
117
|
+
config.global_id.app = "my-app"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Before you rename a model, plan how to carry its acceptances over. Acceptance
|
|
121
|
+
rows are read only through the model API.
|
|
122
|
+
|
|
69
123
|
## Render the host-owned form
|
|
70
124
|
|
|
71
125
|
Keep the page, routes, authorization, document-link markup, and copy in your
|
|
@@ -79,25 +133,33 @@ application. Submit the exact displayed version as a hidden field:
|
|
|
79
133
|
Then record only that version while it remains current:
|
|
80
134
|
|
|
81
135
|
```ruby
|
|
136
|
+
statement = I18n.t("agreements.user_terms.statement")
|
|
137
|
+
|
|
82
138
|
acceptance = Agreements.accept!(
|
|
83
139
|
"user_terms",
|
|
84
140
|
version_id: params.dig(:acceptance, :agreement_version_id),
|
|
85
141
|
subject: current_user,
|
|
86
142
|
actor: current_user,
|
|
87
|
-
authority: "self"
|
|
143
|
+
authority: "self",
|
|
144
|
+
acceptance_statement: statement,
|
|
145
|
+
locale: I18n.locale.to_s
|
|
88
146
|
)
|
|
89
147
|
```
|
|
90
148
|
|
|
91
149
|
`Agreements.accept!` resolves the current version server-side, requires the
|
|
92
150
|
submitted ID to match it, derives opaque keys from the server-owned subject and
|
|
93
|
-
actor, and records
|
|
94
|
-
|
|
151
|
+
actor, and records the exact localized plain-text statement selected by the
|
|
152
|
+
host. Do not accept either value from browser parameters. Retries, double
|
|
153
|
+
clicks, and concurrent submissions return the original acceptance evidence.
|
|
95
154
|
|
|
96
155
|
A missing, malformed, wrong-agreement, or stale ID raises
|
|
97
|
-
`Agreements::VersionNotCurrent`. Its `current_version` is ready to render
|
|
156
|
+
`Agreements::VersionNotCurrent`. Its `current_version` is ready to render. It
|
|
157
|
+
is `nil` when no version exists for that key yet:
|
|
98
158
|
|
|
99
159
|
```ruby
|
|
100
160
|
rescue Agreements::VersionNotCurrent => error
|
|
161
|
+
raise ActiveRecord::RecordNotFound unless error.current_version
|
|
162
|
+
|
|
101
163
|
@version = error.current_version
|
|
102
164
|
render :show, status: :unprocessable_content
|
|
103
165
|
end
|
|
@@ -112,7 +174,9 @@ Agreements.accept!(
|
|
|
112
174
|
version_id: params.dig(:acceptance, :agreement_version_id),
|
|
113
175
|
subject: current_organization,
|
|
114
176
|
actor: current_user,
|
|
115
|
-
authority: "organization_owner"
|
|
177
|
+
authority: "organization_owner",
|
|
178
|
+
acceptance_statement: I18n.t("agreements.organization_dpa.statement"),
|
|
179
|
+
locale: I18n.locale.to_s
|
|
116
180
|
)
|
|
117
181
|
```
|
|
118
182
|
|
|
@@ -142,16 +206,45 @@ def require_organization_dpa
|
|
|
142
206
|
end
|
|
143
207
|
```
|
|
144
208
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
209
|
+
When the agreement is pending:
|
|
210
|
+
|
|
211
|
+
- an HTML or Turbo Stream request is redirected to `location` with
|
|
212
|
+
`303 See Other`, and the method returns `:redirected`;
|
|
213
|
+
- any other request (JSON, CSV, PDF, API) gets an empty `403 Forbidden`, and
|
|
214
|
+
the method returns `:forbidden`.
|
|
215
|
+
|
|
216
|
+
The return value lets you skip work that should not run after a halted
|
|
217
|
+
request, such as Pundit's `verify_authorized`:
|
|
218
|
+
|
|
219
|
+
```ruby
|
|
220
|
+
skip_authorization if require_agreement("organization_dpa", subject: Current.organization, location: path)
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Only GET and HEAD return locations are remembered. A blocked mutation is never
|
|
224
|
+
replayed after acceptance. Consume the safe same-origin path after a successful
|
|
225
|
+
acceptance:
|
|
148
226
|
|
|
149
227
|
```ruby
|
|
150
228
|
redirect_to agreement_return_location || dashboard_path
|
|
151
229
|
```
|
|
152
230
|
|
|
153
|
-
API
|
|
154
|
-
|
|
231
|
+
If your API has its own error format, check before `require_agreement` runs:
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
def require_organization_dpa
|
|
235
|
+
if api_request?
|
|
236
|
+
if Agreements.pending_version("organization_dpa", subject: Current.organization)
|
|
237
|
+
render json: { error: "agreement_required" }, status: :forbidden
|
|
238
|
+
end
|
|
239
|
+
return
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
require_agreement("organization_dpa", subject: Current.organization, location: organization_dpa_path(Current.organization))
|
|
243
|
+
end
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
When no version exists for a key, nothing is pending and nothing is blocked.
|
|
247
|
+
Insert the first version before you rely on enforcement.
|
|
155
248
|
|
|
156
249
|
## Evidence model
|
|
157
250
|
|
|
@@ -168,6 +261,7 @@ the response contract your application supports.
|
|
|
168
261
|
- agreement-version foreign key;
|
|
169
262
|
- opaque subject and actor keys;
|
|
170
263
|
- authority;
|
|
264
|
+
- exact localized acceptance statement and locale;
|
|
171
265
|
- server acceptance time;
|
|
172
266
|
- timestamps.
|
|
173
267
|
|
|
@@ -201,7 +295,7 @@ registry alone does not deploy a new version to an existing installation.
|
|
|
201
295
|
- Draft/publish/activation workflows or an admin dashboard.
|
|
202
296
|
- Electronic signatures or identity proofing.
|
|
203
297
|
- IP addresses, user agents, fingerprints, geolocation, or request provenance.
|
|
204
|
-
- Presentation manifests, snapshots, scroll tracking, or one-time nonces.
|
|
298
|
+
- Presentation manifests, browser snapshots, scroll tracking, or one-time nonces.
|
|
205
299
|
- Consent withdrawal, declarations, attestations, authorizations, retention,
|
|
206
300
|
legal holds, integrity chains, or compliance reporting.
|
|
207
301
|
- Application-specific onboarding, marketing consent, tenancy, ownership,
|
|
@@ -6,7 +6,7 @@ module Agreements
|
|
|
6
6
|
class_name: "Agreements::Version",
|
|
7
7
|
inverse_of: :acceptances
|
|
8
8
|
|
|
9
|
-
validates :subject_key, :actor_key, :authority, :accepted_at, presence: true
|
|
9
|
+
validates :subject_key, :actor_key, :authority, :acceptance_statement, :locale, :accepted_at, presence: true
|
|
10
10
|
validates :authority, format: { with: /\A[a-z0-9_]+\z/ }
|
|
11
11
|
|
|
12
12
|
def readonly?
|
|
@@ -5,6 +5,7 @@ require "uri"
|
|
|
5
5
|
module Agreements
|
|
6
6
|
class Version < ApplicationRecord
|
|
7
7
|
SHA256_PATTERN = /\A[0-9a-f]{64}\z/i
|
|
8
|
+
KEY_PATTERN = /\A[a-z0-9_]+\z/
|
|
8
9
|
|
|
9
10
|
has_many :acceptances,
|
|
10
11
|
class_name: "Agreements::Acceptance",
|
|
@@ -12,7 +13,7 @@ module Agreements
|
|
|
12
13
|
inverse_of: :agreement_version,
|
|
13
14
|
dependent: :restrict_with_exception
|
|
14
15
|
|
|
15
|
-
validates :agreement_key, presence: true, format: { with:
|
|
16
|
+
validates :agreement_key, presence: true, format: { with: KEY_PATTERN }, uniqueness: { scope: :version }
|
|
16
17
|
validates :version, :acceptance_statement, presence: true
|
|
17
18
|
validate :documents_are_external_references
|
|
18
19
|
|
|
@@ -51,13 +52,18 @@ module Agreements
|
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
def allowed_document_attributes?(attributes)
|
|
54
|
-
attributes.keys.all? { |key| %w[title url sha256].include?(key) }
|
|
55
|
+
attributes.keys.all? { |key| %w[key title url sha256].include?(key) }
|
|
55
56
|
end
|
|
56
57
|
|
|
57
58
|
def complete_document_attributes?(attributes)
|
|
58
59
|
attributes["title"].present? &&
|
|
59
60
|
external_https_url?(attributes["url"]) &&
|
|
60
|
-
valid_sha256?(attributes["sha256"])
|
|
61
|
+
valid_sha256?(attributes["sha256"]) &&
|
|
62
|
+
valid_document_key?(attributes["key"])
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def valid_document_key?(key)
|
|
66
|
+
key.nil? || key.to_s.match?(KEY_PATTERN)
|
|
61
67
|
end
|
|
62
68
|
|
|
63
69
|
def external_https_url?(url)
|
|
@@ -9,9 +9,13 @@ module Agreements
|
|
|
9
9
|
private
|
|
10
10
|
|
|
11
11
|
def require_agreement(agreement_key, subject:, location:)
|
|
12
|
-
return unless agreement_enforcement_request?
|
|
13
12
|
return unless Agreements.pending_version(agreement_key, subject: subject)
|
|
14
13
|
|
|
14
|
+
unless agreement_enforcement_request?
|
|
15
|
+
head :forbidden
|
|
16
|
+
return :forbidden
|
|
17
|
+
end
|
|
18
|
+
|
|
15
19
|
remember_agreement_return_location
|
|
16
20
|
redirect_to location, status: :see_other
|
|
17
21
|
:redirected
|
|
@@ -30,8 +34,7 @@ module Agreements
|
|
|
30
34
|
end
|
|
31
35
|
|
|
32
36
|
def agreement_return_location
|
|
33
|
-
|
|
34
|
-
path if path&.start_with?("/") && !path.start_with?("//")
|
|
37
|
+
url_from(session.delete(:return_to_after_agreement))
|
|
35
38
|
end
|
|
36
39
|
end
|
|
37
40
|
end
|
data/lib/agreements/engine.rb
CHANGED
data/lib/agreements/recorder.rb
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
module Agreements
|
|
4
4
|
class Recorder
|
|
5
|
-
def self.call(agreement_version:,
|
|
5
|
+
def self.call(agreement_version:, evidence:)
|
|
6
6
|
Acceptance.create_or_find_by!(
|
|
7
7
|
agreement_version: agreement_version,
|
|
8
|
-
subject_key: Agreements.identity_key(subject)
|
|
8
|
+
subject_key: Agreements.identity_key(evidence.fetch(:subject))
|
|
9
9
|
) do |acceptance|
|
|
10
|
-
acceptance.actor_key = Agreements.identity_key(actor)
|
|
11
|
-
acceptance.authority = authority
|
|
10
|
+
acceptance.actor_key = Agreements.identity_key(evidence.fetch(:actor))
|
|
11
|
+
acceptance.authority = evidence.fetch(:authority)
|
|
12
|
+
acceptance.acceptance_statement = evidence.fetch(:acceptance_statement)
|
|
13
|
+
acceptance.locale = evidence.fetch(:locale)
|
|
12
14
|
acceptance.accepted_at = Time.current
|
|
13
15
|
end
|
|
14
16
|
end
|
data/lib/agreements/version.rb
CHANGED
data/lib/agreements.rb
CHANGED
|
@@ -23,15 +23,21 @@ module Agreements
|
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
# Explicit keywords keep this evidence boundary distinct from browser params.
|
|
27
|
+
# rubocop:disable-next Metrics/ParameterLists
|
|
28
|
+
def accept!(agreement_key, version_id:, subject:, actor:, authority:, acceptance_statement:, locale:)
|
|
27
29
|
version = current_version(agreement_key)
|
|
28
30
|
raise VersionNotCurrent, version unless version&.id.to_s == version_id.to_s
|
|
29
31
|
|
|
30
32
|
Recorder.call(
|
|
31
33
|
agreement_version: version,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
evidence: {
|
|
35
|
+
subject: subject,
|
|
36
|
+
actor: actor,
|
|
37
|
+
authority: authority,
|
|
38
|
+
acceptance_statement: acceptance_statement,
|
|
39
|
+
locale: locale
|
|
40
|
+
}
|
|
35
41
|
)
|
|
36
42
|
end
|
|
37
43
|
|
|
@@ -21,6 +21,8 @@ class CreateAgreementsTables < ActiveRecord::Migration<%= migration_version %>
|
|
|
21
21
|
t.string :subject_key, null: false
|
|
22
22
|
t.string :actor_key, null: false
|
|
23
23
|
t.string :authority, null: false
|
|
24
|
+
t.text :acceptance_statement, null: false
|
|
25
|
+
t.string :locale, null: false
|
|
24
26
|
t.datetime :accepted_at, null: false
|
|
25
27
|
t.timestamps
|
|
26
28
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: agreements
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yaroslav Shmarov
|
|
@@ -10,7 +10,61 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: actionpack
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.1'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '9'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '7.1'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '9'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: activerecord
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - ">="
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '7.1'
|
|
39
|
+
- - "<"
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: '9'
|
|
42
|
+
type: :runtime
|
|
43
|
+
prerelease: false
|
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '7.1'
|
|
49
|
+
- - "<"
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: '9'
|
|
52
|
+
- !ruby/object:Gem::Dependency
|
|
53
|
+
name: globalid
|
|
54
|
+
requirement: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '1.0'
|
|
59
|
+
type: :runtime
|
|
60
|
+
prerelease: false
|
|
61
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
62
|
+
requirements:
|
|
63
|
+
- - ">="
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: '1.0'
|
|
66
|
+
- !ruby/object:Gem::Dependency
|
|
67
|
+
name: railties
|
|
14
68
|
requirement: !ruby/object:Gem::Requirement
|
|
15
69
|
requirements:
|
|
16
70
|
- - ">="
|
|
@@ -31,10 +85,10 @@ dependencies:
|
|
|
31
85
|
version: '9'
|
|
32
86
|
description: |
|
|
33
87
|
A small Rails engine that stores immutable external agreement-version
|
|
34
|
-
metadata and append-only acceptance evidence. It distinguishes
|
|
35
|
-
bound by an agreement from the actor accepting under recorded
|
|
36
|
-
rejects stale rendered versions, and leaves documents,
|
|
37
|
-
authorization, routes, and UI in the host application.
|
|
88
|
+
metadata and append-only localized acceptance evidence. It distinguishes
|
|
89
|
+
the subject bound by an agreement from the actor accepting under recorded
|
|
90
|
+
authority, rejects stale rendered versions, and leaves documents,
|
|
91
|
+
authentication, authorization, routes, and UI in the host application.
|
|
38
92
|
email:
|
|
39
93
|
- yashm@outlook.com
|
|
40
94
|
executables: []
|
|
@@ -81,7 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
81
135
|
- !ruby/object:Gem::Version
|
|
82
136
|
version: '0'
|
|
83
137
|
requirements: []
|
|
84
|
-
rubygems_version: 4.0.
|
|
138
|
+
rubygems_version: 4.0.20
|
|
85
139
|
specification_version: 4
|
|
86
140
|
summary: Auditable acceptance of externally hosted legal agreements for Rails.
|
|
87
141
|
test_files: []
|