active_sanction 1.0.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.
Files changed (143) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +538 -0
  3. data/CODE_OF_CONDUCT.md +74 -0
  4. data/CONTRIBUTING.md +312 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +616 -0
  7. data/SECURITY.md +97 -0
  8. data/active_sanction.gemspec +98 -0
  9. data/docs/adding_a_source.md +1083 -0
  10. data/docs/api_stability.md +357 -0
  11. data/docs/bundle_format.md +407 -0
  12. data/lib/active_sanction/address.rb +170 -0
  13. data/lib/active_sanction/client.rb +341 -0
  14. data/lib/active_sanction/configuration.rb +721 -0
  15. data/lib/active_sanction/countries.txt +292 -0
  16. data/lib/active_sanction/country.rb +164 -0
  17. data/lib/active_sanction/deprecation.rb +168 -0
  18. data/lib/active_sanction/diff/change.rb +208 -0
  19. data/lib/active_sanction/diff.rb +348 -0
  20. data/lib/active_sanction/doctor/checkup.rb +356 -0
  21. data/lib/active_sanction/doctor/diagnosis.rb +245 -0
  22. data/lib/active_sanction/doctor/finding.rb +183 -0
  23. data/lib/active_sanction/doctor/profile.rb +384 -0
  24. data/lib/active_sanction/doctor/report.rb +227 -0
  25. data/lib/active_sanction/doctor.rb +347 -0
  26. data/lib/active_sanction/entity.rb +293 -0
  27. data/lib/active_sanction/error.rb +332 -0
  28. data/lib/active_sanction/fetcher/result.rb +116 -0
  29. data/lib/active_sanction/fetcher.rb +242 -0
  30. data/lib/active_sanction/http_client/errors.rb +94 -0
  31. data/lib/active_sanction/http_client/response.rb +129 -0
  32. data/lib/active_sanction/http_client.rb +324 -0
  33. data/lib/active_sanction/identifier.rb +212 -0
  34. data/lib/active_sanction/index/builder.rb +89 -0
  35. data/lib/active_sanction/index/candidate.rb +63 -0
  36. data/lib/active_sanction/index/entry.rb +66 -0
  37. data/lib/active_sanction/index/features.rb +112 -0
  38. data/lib/active_sanction/index.rb +381 -0
  39. data/lib/active_sanction/match_result.rb +345 -0
  40. data/lib/active_sanction/matcher.rb +388 -0
  41. data/lib/active_sanction/name.rb +183 -0
  42. data/lib/active_sanction/normalizer/cache.rb +82 -0
  43. data/lib/active_sanction/normalizer/dictionaries/honorifics.txt +30 -0
  44. data/lib/active_sanction/normalizer/dictionaries/legal_forms.txt +72 -0
  45. data/lib/active_sanction/normalizer/dictionaries/organization_stopwords.txt +20 -0
  46. data/lib/active_sanction/normalizer/dictionaries/particles.txt +35 -0
  47. data/lib/active_sanction/normalizer/dictionary/stoplist.rb +121 -0
  48. data/lib/active_sanction/normalizer/dictionary.rb +243 -0
  49. data/lib/active_sanction/normalizer/form.rb +279 -0
  50. data/lib/active_sanction/normalizer.rb +137 -0
  51. data/lib/active_sanction/parsers/column_shape.rb +274 -0
  52. data/lib/active_sanction/parsers/delimited_table/reader.rb +194 -0
  53. data/lib/active_sanction/parsers/delimited_table/row.rb +81 -0
  54. data/lib/active_sanction/parsers/delimited_table.rb +151 -0
  55. data/lib/active_sanction/parsers/format.rb +107 -0
  56. data/lib/active_sanction/parsers/join.rb +149 -0
  57. data/lib/active_sanction/parsers/spreadsheet/archive.rb +258 -0
  58. data/lib/active_sanction/parsers/spreadsheet/reader.rb +213 -0
  59. data/lib/active_sanction/parsers/spreadsheet/row.rb +88 -0
  60. data/lib/active_sanction/parsers/spreadsheet/workbook.rb +314 -0
  61. data/lib/active_sanction/parsers/spreadsheet.rb +168 -0
  62. data/lib/active_sanction/parsers/xml_records/backends/nokogiri.rb +134 -0
  63. data/lib/active_sanction/parsers/xml_records/backends/rexml.rb +157 -0
  64. data/lib/active_sanction/parsers/xml_records/backends.rb +103 -0
  65. data/lib/active_sanction/parsers/xml_records/builder.rb +90 -0
  66. data/lib/active_sanction/parsers/xml_records/reader.rb +134 -0
  67. data/lib/active_sanction/parsers/xml_records/record.rb +174 -0
  68. data/lib/active_sanction/parsers/xml_records.rb +140 -0
  69. data/lib/active_sanction/parsers.rb +94 -0
  70. data/lib/active_sanction/partial_date/parser.rb +124 -0
  71. data/lib/active_sanction/partial_date.rb +303 -0
  72. data/lib/active_sanction/payload_cache/checksum.rb +66 -0
  73. data/lib/active_sanction/payload_cache/entry.rb +304 -0
  74. data/lib/active_sanction/payload_cache.rb +366 -0
  75. data/lib/active_sanction/phonetics/double_metaphone.rb +662 -0
  76. data/lib/active_sanction/phonetics.rb +40 -0
  77. data/lib/active_sanction/query.rb +290 -0
  78. data/lib/active_sanction/rescreen/alert.rb +314 -0
  79. data/lib/active_sanction/rescreen.rb +332 -0
  80. data/lib/active_sanction/scorer/adjustments.rb +241 -0
  81. data/lib/active_sanction/scorer/name_score.rb +279 -0
  82. data/lib/active_sanction/scorer/reason.rb +136 -0
  83. data/lib/active_sanction/scorer/result.rb +129 -0
  84. data/lib/active_sanction/scorer/subject.rb +235 -0
  85. data/lib/active_sanction/scorer/weights.rb +306 -0
  86. data/lib/active_sanction/scorer.rb +303 -0
  87. data/lib/active_sanction/similarity/jaro_winkler.rb +210 -0
  88. data/lib/active_sanction/similarity/levenshtein.rb +179 -0
  89. data/lib/active_sanction/similarity/token_set.rb +159 -0
  90. data/lib/active_sanction/similarity/token_sort.rb +120 -0
  91. data/lib/active_sanction/similarity.rb +183 -0
  92. data/lib/active_sanction/snapshot/bundle/header.rb +302 -0
  93. data/lib/active_sanction/snapshot/bundle/payload.rb +185 -0
  94. data/lib/active_sanction/snapshot/bundle/signature.rb +201 -0
  95. data/lib/active_sanction/snapshot/bundle.rb +381 -0
  96. data/lib/active_sanction/snapshot.rb +305 -0
  97. data/lib/active_sanction/sources/australia_dfat/published_date.rb +166 -0
  98. data/lib/active_sanction/sources/australia_dfat/record.rb +319 -0
  99. data/lib/active_sanction/sources/australia_dfat.rb +291 -0
  100. data/lib/active_sanction/sources/base.rb +295 -0
  101. data/lib/active_sanction/sources/canada_sema/record.rb +282 -0
  102. data/lib/active_sanction/sources/canada_sema/source_ref.rb +89 -0
  103. data/lib/active_sanction/sources/canada_sema.rb +153 -0
  104. data/lib/active_sanction/sources/definition.rb +360 -0
  105. data/lib/active_sanction/sources/eu_fsf/record.rb +467 -0
  106. data/lib/active_sanction/sources/eu_fsf.rb +203 -0
  107. data/lib/active_sanction/sources/ofac/record.rb +251 -0
  108. data/lib/active_sanction/sources/ofac/remarks_parser/coverage.rb +120 -0
  109. data/lib/active_sanction/sources/ofac/remarks_parser/vocabulary.rb +121 -0
  110. data/lib/active_sanction/sources/ofac/remarks_parser.rb +312 -0
  111. data/lib/active_sanction/sources/ofac.rb +248 -0
  112. data/lib/active_sanction/sources/ofac_consolidated/record.rb +37 -0
  113. data/lib/active_sanction/sources/ofac_consolidated.rb +203 -0
  114. data/lib/active_sanction/sources/ofac_sdn.rb +43 -0
  115. data/lib/active_sanction/sources/remarks.rb +84 -0
  116. data/lib/active_sanction/sources/uk_sanctions_list/published_date.rb +104 -0
  117. data/lib/active_sanction/sources/uk_sanctions_list/record.rb +412 -0
  118. data/lib/active_sanction/sources/uk_sanctions_list.rb +214 -0
  119. data/lib/active_sanction/sources/un_consolidated/record.rb +279 -0
  120. data/lib/active_sanction/sources/un_consolidated.rb +140 -0
  121. data/lib/active_sanction/sources.rb +196 -0
  122. data/lib/active_sanction/storage/active_record/reader.rb +124 -0
  123. data/lib/active_sanction/storage/active_record/row.rb +175 -0
  124. data/lib/active_sanction/storage/active_record/writer.rb +141 -0
  125. data/lib/active_sanction/storage/active_record.rb +277 -0
  126. data/lib/active_sanction/storage/base.rb +216 -0
  127. data/lib/active_sanction/storage/file_system.rb +373 -0
  128. data/lib/active_sanction/storage/memory.rb +67 -0
  129. data/lib/active_sanction/storage/meta.rb +188 -0
  130. data/lib/active_sanction/storage.rb +74 -0
  131. data/lib/active_sanction/subject.rb +271 -0
  132. data/lib/active_sanction/sync/report.rb +245 -0
  133. data/lib/active_sanction/sync/result.rb +296 -0
  134. data/lib/active_sanction/sync.rb +390 -0
  135. data/lib/active_sanction/validator_store/file_system.rb +103 -0
  136. data/lib/active_sanction/validator_store/memory.rb +41 -0
  137. data/lib/active_sanction/validator_store.rb +119 -0
  138. data/lib/active_sanction/validators.rb +195 -0
  139. data/lib/active_sanction/version.rb +23 -0
  140. data/lib/active_sanction.rb +331 -0
  141. data/lib/generators/active_sanction/install/install_generator.rb +60 -0
  142. data/lib/generators/active_sanction/install/templates/create_active_sanction_tables.rb.tt +142 -0
  143. metadata +236 -0
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,312 @@
1
+ # Contributing to ActiveSanction
2
+
3
+ Bug reports and pull requests are welcome at
4
+ <https://github.com/Babystep-Technologies/active_sanction>. This file is the
5
+ whole of what a contributor needs: what is worth working on, how to get from a
6
+ clone to a green suite, what the checks are, and how to sign a commit.
7
+
8
+ ## What is open, and what is not
9
+
10
+ **The gem is complete on its own, and stays that way.** Every public sanctions
11
+ list it supports is fetchable, parseable and screenable with the gem alone,
12
+ forever, with no account and no key. The entity model, the fetch layer, every
13
+ source adapter, storage, the whole matching engine with its weights and
14
+ dictionaries, sync, diff, rescreening and the bundle format are all in this
15
+ repository, and they are the whole of what we run.
16
+
17
+ **A commercial hosted service exists, and what it sells is operations rather
18
+ than capability** — data kept fresh through upstream breakage, an availability
19
+ guarantee, continuous monitoring of a book of business, and a retained audit
20
+ trail. Nothing is withheld from the gem to make it more attractive.
21
+
22
+ The rule that settles scope questions: **anything that runs on a user's machine
23
+ against public data belongs in the gem; anything that requires somebody to
24
+ operate infrastructure belongs in the service.** A contribution is never
25
+ declined for being too useful, and no contribution is relicensed — see
26
+ [Sign your work](#sign-your-work), which is a sign-off rather than a
27
+ contributor licence agreement precisely because there is no relicensing right
28
+ to reserve.
29
+
30
+ ## Where documentation goes
31
+
32
+ Reference and explanation belong on [the documentation site](https://babystep-technologies.github.io/active_sanction/); the README links to them rather than restating them. The README stays the narrative and the quickstart — what stays there, what stays disclaimer, what a reader needs to evaluate the library without leaving GitHub — and everything else that the site has taken ownership of leaves the README as a one-line pointer. If a change touches a section the site already covers, update the site page rather than growing the README back; if a fact would then exist in full in both places, that is a bug, not a style choice.
33
+
34
+ ## The two most useful contributions
35
+
36
+ **A new list.** [`docs/adding_a_source.md`](docs/adding_a_source.md) is written
37
+ for exactly that, and the shared conformance group means an adapter written
38
+ outside this repository is held to the same checklist the shipped ones are.
39
+ The documentation site has a shorter
40
+ [field guide](https://babystep-technologies.github.io/active_sanction/how-to/adding-a-source/)
41
+ version, pointing into the file's sections rather than restating them. A
42
+ source can also be registered from your own application without touching this
43
+ gem at all, which is the point of the extension seam — open an issue first if
44
+ you would rather have it shipped here, so two people do not write the same
45
+ adapter.
46
+
47
+ **A name this version gets wrong.** A missed record or a false alert belongs in
48
+ [`benchmark/fixtures/labeled_set.yml`](benchmark/fixtures/labeled_set.yml)
49
+ with what it is supposed to find, whether or not the matching is changed in the
50
+ same pull request. A case nobody has written down is a case that regresses
51
+ silently, and the accuracy report is the only place a two-point weight change
52
+ becomes visible.
53
+
54
+ ## If you are working with a coding agent
55
+
56
+ Both of the procedures above are written down for one, in
57
+ [`.claude/skills/`](.claude/skills)
58
+ ([#113](https://github.com/Babystep-Technologies/active_sanction/issues/113)):
59
+ [`adding-a-source`](.claude/skills/adding-a-source/SKILL.md) for a list the gem
60
+ does not read yet, and
61
+ [`repairing-a-source`](.claude/skills/repairing-a-source/SKILL.md) for a
62
+ publisher that changed its file. The rules they are both held to are one file,
63
+ [`.claude/rules/adapter-rules.md`](.claude/rules/adapter-rules.md), and
64
+ [`AGENTS.md`](AGENTS.md) is the entry point for a tool that does not read
65
+ Claude skills.
66
+
67
+ They exist because `docs/adding_a_source.md` is written for a human reading it
68
+ start to finish and an agent does not read it that way — it greps, finds
69
+ section 5, and misses the three rules in section 6 that make an id stable. The
70
+ failure is silent: the adapter parses, the spec passes, and the ids change on
71
+ every sync. None of it is a second copy of the reasoning; every rule links back
72
+ to the section that argues for it, and
73
+ [`spec/agent_instructions_spec.rb`](spec/agent_instructions_spec.rb) fails the
74
+ build when one of those links stops resolving. Nothing under `.claude/` is
75
+ packaged into the gem.
76
+
77
+ Reading them costs a contributor who does not use an agent nothing, and they
78
+ are worth a look either way: they are the checklist form of everything below.
79
+
80
+ ## From a clone to a green suite
81
+
82
+ Ruby 3.1 or newer. The version this gem is developed on is in
83
+ [`.ruby-version`](.ruby-version), and CI builds every series from the floor
84
+ up — 3.1, 3.2, 3.3, 3.4 and 4.0 — plus `ruby-head`, which is allowed to fail:
85
+ a change upstream is news rather than a broken build, so that job reports in
86
+ the checks list and leaves the badge green.
87
+ [`spec/supported_rubies_spec.rb`](spec/supported_rubies_spec.rb) reads that
88
+ matrix, `.ruby-version`, the gemspec's `required_ruby_version` and RuboCop's
89
+ `TargetRubyVersion`, and fails when they stop agreeing — the version a
90
+ change is written on being the one version no build ever ran is how that
91
+ drift stays invisible
92
+ ([#80](https://github.com/Babystep-Technologies/active_sanction/issues/80)).
93
+
94
+ $ git clone https://github.com/Babystep-Technologies/active_sanction.git
95
+ $ cd active_sanction
96
+ $ bin/setup # bundle install
97
+ $ bundle exec rake # specs, RuboCop, Sorbet -- all three must pass
98
+
99
+ `bin/console` gives a prompt with the library loaded. Nothing else is required:
100
+ the suite is hermetic, so a green run needs no network, no credentials and no
101
+ synced data.
102
+
103
+ ## The three checks
104
+
105
+ `bundle exec rake` runs RSpec, then RuboCop, then `srb tc`. CI runs the same
106
+ three on every Ruby the gem supports, plus `ruby-head`, which is allowed to
107
+ fail.
108
+
109
+ **The suite never reaches the internet.** `spec_helper.rb` calls
110
+ `WebMock.disable_net_connect!`, so an un-stubbed HTTP call raises rather than
111
+ quietly fetching a government file. Parser specs run against committed
112
+ fixtures. A spec that genuinely needs a real endpoint is tagged `:live`, is
113
+ excluded from the default run, and is never run in CI — a red build should mean
114
+ our code broke, not that a source went down.
115
+
116
+ $ bundle exec rspec --tag live
117
+
118
+ **Every file in `lib/` is `# typed: strict`,** and new files are born that way.
119
+ A signature written beside the code costs a line; one retrofitted a milestone
120
+ later costs an afternoon of reading the code back.
121
+
122
+ **If you change the normalizer, the index, the similarity algorithms, the
123
+ scorer or the weights,** run the accuracy benchmark and commit the report it
124
+ rewrites:
125
+
126
+ $ bundle exec rake benchmark:accuracy
127
+
128
+ [`benchmark/results/accuracy.md`](benchmark/results/accuracy.md) is committed
129
+ for the same reason the source baselines are: a diff there is a change in what
130
+ this library finds, which is otherwise invisible in a code review.
131
+
132
+ ## Static typing
133
+
134
+ Every file in `lib/` is `# typed: strict`, and new files are born that way: a
135
+ signature written beside the code costs a line, and one retrofitted a
136
+ milestone later costs an afternoon of reading the code back.
137
+
138
+ $ bundle exec srb tc # or `bundle exec rake`, which runs it last
139
+
140
+ `sorbet-runtime` is a dependency of the gem, because the signatures are
141
+ inline `sig` blocks and inline `sig` blocks are ordinary method calls. It is
142
+ pure Ruby and compiles nothing, so it clears the same bar the gemspec sets
143
+ for Nokogiri. The static half — `sorbet` and `tapioca` — is in the Gemfile
144
+ and never reaches an application.
145
+
146
+ **What the types are for, and where they deliberately stop.** The canonical
147
+ model is declared: `Entity` states that `dates_of_birth` is an array of
148
+ `PartialDate`, so an adapter handing over the string a publisher wrote is a
149
+ type error rather than a bug found three layers downstream. The runtime half
150
+ of a signature is shallow — it sees the Array and not what is in it — so the
151
+ adapter conformance group goes on asserting the element types per fixture,
152
+ which is what covers an adapter written outside this repository. Everything
153
+ a publisher wrote is `T.untyped` on the way in, because the value objects
154
+ already coerce it and raise `InvalidArgument` — an `ArgumentError`, so the
155
+ code around this library keeps its existing rescue — with messages written
156
+ for whoever has to fix the record, and a type error would say less. Three
157
+ places are `T.untyped` on purpose and say why in a comment where they sit:
158
+ the source registry (duck-typed on `.key` and `.new`, which is what makes a
159
+ bank's internal watchlist a first-class source), `XmlRecords::Backends`
160
+ (same, for a backend registered from outside), and `Snapshot#entities` (the
161
+ storage conformance group builds a snapshot of half-deserialized hashes on
162
+ purpose, to prove it catches a store that hands them back).
163
+
164
+ A host that wants none of it can turn every check off before requiring the
165
+ gem, which is supported and tested:
166
+
167
+ ```ruby
168
+ T::Configuration.default_checked_level = :never
169
+ require "active_sanction"
170
+ ```
171
+
172
+ **Consumers who typecheck their own code** need nothing from us but the gem:
173
+
174
+ $ bundle exec tapioca gem active_sanction
175
+
176
+ reads the inline signatures through `sorbet-runtime` and writes an RBI that
177
+ says what this version actually declares. No `rbi/active_sanction.rbi` is
178
+ shipped, deliberately — a hand-maintained copy of the signatures would be a
179
+ second source of truth, and a signature that lies is worse than none.
180
+
181
+ The RBIs under `sorbet/` are the checker's own working files — generated
182
+ definitions for the gems `lib/` reaches, plus one hand-written shim for the
183
+ Rails generator surface — and are excluded from the packaged gem. Regenerate
184
+ one with `bin/tapioca gem <name>`.
185
+
186
+ ## Benchmarks and the upstream canary
187
+
188
+ `benchmark/` holds measurements that answer a design question rather than
189
+ pass or fail, so they are not part of `rake`:
190
+
191
+ $ bundle exec rake benchmark:similarity # the matching algorithms
192
+ $ bundle exec rake benchmark:index # index build, memory, query latency
193
+ $ bundle exec rake benchmark:scorer # scoring latency, and what a threshold buys
194
+ $ bundle exec rake benchmark:accuracy # precision, recall and F1 against the labeled set
195
+ $ bundle exec rake benchmark:latency # what a whole screening call costs
196
+ $ bundle exec rake benchmark:rescreen # applying a diff to a book, against the naive full rescreen
197
+ $ RUBYOPT=--yjit bundle exec rake benchmark:similarity
198
+
199
+ The labeled set behind `benchmark:accuracy` is
200
+ [`benchmark/fixtures/labeled_set.yml`](benchmark/fixtures/labeled_set.yml):
201
+ 87 queries against the real published records the source fixtures hold, each
202
+ one labeled with what it is supposed to find and what kind of damage it is
203
+ doing to the name. Both harnesses hide the labeled records inside a
204
+ synthetic corpus the size and shape of the real lists; run either against a
205
+ real synced corpus instead with `BACKGROUND=store bundle exec rake
206
+ benchmark:accuracy`.
207
+
208
+ **The upstream canary** is a scheduled workflow that fetches every list from
209
+ its real publisher on weekdays and compares what it measures against the
210
+ baselines committed under [`.github/baselines`](.github/baselines)
211
+ ([#69](https://github.com/Babystep-Technologies/active_sanction/issues/69)),
212
+ filing an issue when a government has changed something the gem must adapt
213
+ to. It never runs as part of CI and never turns the CI badge red — a red
214
+ build should mean our code broke, not that a source went down.
215
+ [Detect when a publisher changes its format](https://babystep-technologies.github.io/active_sanction/how-to/detecting-format-drift/#the-upstream-canary-the-same-idea-run-on-a-schedule-against-real-endpoints)
216
+ has the commands and the full mechanics; when adding a new source, commit
217
+ its baseline in the same pull request as the adapter.
218
+
219
+ ## API documentation
220
+
221
+ $ bundle exec rake doc # renders doc/
222
+ $ bundle exec yard stats --list-undoc
223
+
224
+ Every public module, class, method and attribute in `lib/` carries a
225
+ comment, and `rake doc` renders them. Types are not written twice:
226
+ `yard-sorbet` reads the inline `sig` blocks and turns them into `@param` and
227
+ `@return`, so the signature the checker reads is the signature the
228
+ documentation shows. What is deliberately left undocumented is internal
229
+ constants — column names, regex fragments, the `MEMBERS` lists the value
230
+ objects serialize through — named for the code that reads them, where a
231
+ comment restating the name would be noise.
232
+
233
+ ## Releasing
234
+
235
+ `bundle exec rake install` installs the gem locally. A release is: bump
236
+ `VERSION` in [`lib/active_sanction/version.rb`](lib/active_sanction/version.rb),
237
+ move the `Unreleased` section of [`CHANGELOG.md`](CHANGELOG.md) under the new
238
+ version with its date, then `bundle exec rake release`, which tags, pushes
239
+ and publishes to [rubygems.org](https://rubygems.org).
240
+
241
+ `MATCHER_VERSION` in the same file is bumped on a different occasion and for
242
+ a different reason — whenever a change to the normalizer, the index, the
243
+ similarity algorithms or the scorer could move a score — because an auditor
244
+ asking "would this screening come out the same today?" needs the answer to
245
+ that specific question rather than a release number that also answers
246
+ several others.
247
+
248
+ ## Sign your work
249
+
250
+ Contributions are accepted under the
251
+ [Developer Certificate of Origin](https://developercertificate.org) 1.1. It is
252
+ a statement that you wrote the patch or otherwise have the right to submit it
253
+ under this project's licence, and it is made by adding one trailer to the
254
+ commit message:
255
+
256
+ Signed-off-by: Your Name <your.email@example.com>
257
+
258
+ Git writes it for you:
259
+
260
+ $ git commit -s
261
+
262
+ The name and address must be real and must match the commit author, because the
263
+ sign-off is what records who certified the contribution. Pseudonyms are fine;
264
+ anonymous contributions are not.
265
+
266
+ **A pull request whose commits are not all signed off fails CI,** and the
267
+ failing job prints the fix. To sign off work you have already committed:
268
+
269
+ $ git rebase --signoff origin/main
270
+ $ git push --force-with-lease
271
+
272
+ **There is no contributor licence agreement.** The commercial advantage here is
273
+ operational rather than code secrecy, so there is no need to reserve a right to
274
+ relicense what you send. The DCO gives clean provenance for every contribution
275
+ and costs a contributor one flag.
276
+
277
+ ## Opening a pull request
278
+
279
+ One change per pull request, with a message that says what moved and why.
280
+ Describe the reasoning in the commit message rather than only in the pull
281
+ request body — a commit message survives in `git log` after the forge that
282
+ hosted the review has changed.
283
+
284
+ Specs come with the change rather than after it. A bug fix arrives with the
285
+ spec that would have caught the bug.
286
+
287
+ ## Security-relevant bugs
288
+
289
+ **Do not open a public issue for one.** A screening library reporting a false
290
+ negative is a security-relevant bug: somebody may be relying on an empty result
291
+ to clear a payment. [`SECURITY.md`](SECURITY.md) says what is in scope, how to
292
+ report privately, and what response to expect.
293
+
294
+ ## The name
295
+
296
+ **The code licence grants no rights in the name.** ActiveSanction, the project
297
+ name and any associated branding are not licensed by
298
+ [the MIT licence](LICENSE.txt), which covers the software and nothing else.
299
+ A fork, a derivative work or a plugin may say that it is built on, compatible
300
+ with, or derived from ActiveSanction; it may not use the name in a way that
301
+ suggests it is this project or is endorsed by it. Anyone can produce and
302
+ distribute a sanctions snapshot bundle in [the published format](docs/bundle_format.md)
303
+ under the same rule.
304
+
305
+ This is the ordinary position under MIT, which grants no trademark rights
306
+ either way. It is written down only so nobody has to guess.
307
+
308
+ ## Code of conduct
309
+
310
+ This project is intended to be a safe, welcoming space for collaboration.
311
+ Everyone interacting with it is expected to follow the
312
+ [code of conduct](CODE_OF_CONDUCT.md).
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Marshall Shen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.