agreements 0.1.0 → 0.2.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: 6d0c0ec7930db8014c275731beebb35381ce05acc26e7b56ec68e3a6e1d8dcd1
4
- data.tar.gz: ec47850af89352033e3ff2f9458073fcc33508b6cfeaddb97687a9f969b3b828
3
+ metadata.gz: cbb4e3cb21a8e743ea9fab48f117242cad49d78e752258bbb992894498355be4
4
+ data.tar.gz: f2160a49ff3799851018060c773643c9b829bad4a1d3e22944913cef62dae501
5
5
  SHA512:
6
- metadata.gz: 48879a71eb8489ee500f01172ba0e25488730aeb263c41478b9f3f910cf6a7c6c12bd7c1ae03e8244b65fe9f733ec9e2949010ac0482ba51d1bcf59313663a65
7
- data.tar.gz: a67ad2a4e8282b7b8a1f1fe71842e3797123eb3611d1a36c4dd60b163085a325846797431ff67c92aca9224fba3bd5c2cacaca804dd0255bc24628508f3d4351
6
+ metadata.gz: 70299f73fe7bc2b6760aae7b1389fa3913298175554ac001963270372c97af651bec611613ae51d367a320dd6d785b154517cccc1c468eece83f7b25e3c0212c
7
+ data.tar.gz: 7a27e58cf17b1068ecee2705f510f38f6900829eb77c5177b8a77237cb19be6e581b728f8a2eba56f973e8850977fa72e612af28dbdbafd1d787d11d86ad2e60
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0 - 2026-08-27
4
+
5
+ - Record the exact localized acceptance statement and locale shown by the host.
6
+ - Publish releases through RubyGems trusted publishing.
7
+
3
8
  ## 0.1.0 - 2026-08-25
4
9
 
5
10
  - 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, who performed the
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,6 +29,13 @@ 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.1
33
+
34
+ Version 0.2 adds non-null `acceptance_statement` and `locale` columns to
35
+ `agreements_acceptances`. Add them in a host migration before upgrading. For
36
+ existing evidence, copy each version's canonical statement and use an honest
37
+ locale such as `und` when the displayed locale is unknown.
38
+
32
39
  ## Define a version
33
40
 
34
41
  Agreement versions are deployed data. Create them in a data migration and keep
@@ -79,19 +86,24 @@ application. Submit the exact displayed version as a hidden field:
79
86
  Then record only that version while it remains current:
80
87
 
81
88
  ```ruby
89
+ statement = I18n.t("agreements.user_terms.statement")
90
+
82
91
  acceptance = Agreements.accept!(
83
92
  "user_terms",
84
93
  version_id: params.dig(:acceptance, :agreement_version_id),
85
94
  subject: current_user,
86
95
  actor: current_user,
87
- authority: "self"
96
+ authority: "self",
97
+ acceptance_statement: statement,
98
+ locale: I18n.locale.to_s
88
99
  )
89
100
  ```
90
101
 
91
102
  `Agreements.accept!` resolves the current version server-side, requires the
92
103
  submitted ID to match it, derives opaque keys from the server-owned subject and
93
- actor, and records one acceptance per subject and version. Retries, double
94
- clicks, and concurrent submissions return the original acceptance.
104
+ actor, and records the exact localized plain-text statement selected by the
105
+ host. Do not accept either value from browser parameters. Retries, double
106
+ clicks, and concurrent submissions return the original acceptance evidence.
95
107
 
96
108
  A missing, malformed, wrong-agreement, or stale ID raises
97
109
  `Agreements::VersionNotCurrent`. Its `current_version` is ready to render:
@@ -112,7 +124,9 @@ Agreements.accept!(
112
124
  version_id: params.dig(:acceptance, :agreement_version_id),
113
125
  subject: current_organization,
114
126
  actor: current_user,
115
- authority: "organization_owner"
127
+ authority: "organization_owner",
128
+ acceptance_statement: I18n.t("agreements.organization_dpa.statement"),
129
+ locale: I18n.locale.to_s
116
130
  )
117
131
  ```
118
132
 
@@ -168,6 +182,7 @@ the response contract your application supports.
168
182
  - agreement-version foreign key;
169
183
  - opaque subject and actor keys;
170
184
  - authority;
185
+ - exact localized acceptance statement and locale;
171
186
  - server acceptance time;
172
187
  - timestamps.
173
188
 
@@ -201,7 +216,7 @@ registry alone does not deploy a new version to an existing installation.
201
216
  - Draft/publish/activation workflows or an admin dashboard.
202
217
  - Electronic signatures or identity proofing.
203
218
  - IP addresses, user agents, fingerprints, geolocation, or request provenance.
204
- - Presentation manifests, snapshots, scroll tracking, or one-time nonces.
219
+ - Presentation manifests, browser snapshots, scroll tracking, or one-time nonces.
205
220
  - Consent withdrawal, declarations, attestations, authorizations, retention,
206
221
  legal holds, integrity chains, or compliance reporting.
207
222
  - 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?
@@ -2,13 +2,15 @@
2
2
 
3
3
  module Agreements
4
4
  class Recorder
5
- def self.call(agreement_version:, subject:, actor:, authority:)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Agreements
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/agreements.rb CHANGED
@@ -23,15 +23,21 @@ module Agreements
23
23
  end
24
24
  end
25
25
 
26
- def accept!(agreement_key, version_id:, subject:, actor:, authority:)
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
- subject: subject,
33
- actor: actor,
34
- authority: authority
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.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yaroslav Shmarov
@@ -31,10 +31,10 @@ dependencies:
31
31
  version: '9'
32
32
  description: |
33
33
  A small Rails engine that stores immutable external agreement-version
34
- metadata and append-only acceptance evidence. It distinguishes the subject
35
- bound by an agreement from the actor accepting under recorded authority,
36
- rejects stale rendered versions, and leaves documents, authentication,
37
- authorization, routes, and UI in the host application.
34
+ metadata and append-only localized acceptance evidence. It distinguishes
35
+ the subject bound by an agreement from the actor accepting under recorded
36
+ authority, rejects stale rendered versions, and leaves documents,
37
+ authentication, authorization, routes, and UI in the host application.
38
38
  email:
39
39
  - yashm@outlook.com
40
40
  executables: []
@@ -81,7 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  requirements: []
84
- rubygems_version: 4.0.10
84
+ rubygems_version: 4.0.16
85
85
  specification_version: 4
86
86
  summary: Auditable acceptance of externally hosted legal agreements for Rails.
87
87
  test_files: []