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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +538 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +312 -0
- data/LICENSE.txt +21 -0
- data/README.md +616 -0
- data/SECURITY.md +97 -0
- data/active_sanction.gemspec +98 -0
- data/docs/adding_a_source.md +1083 -0
- data/docs/api_stability.md +357 -0
- data/docs/bundle_format.md +407 -0
- data/lib/active_sanction/address.rb +170 -0
- data/lib/active_sanction/client.rb +341 -0
- data/lib/active_sanction/configuration.rb +721 -0
- data/lib/active_sanction/countries.txt +292 -0
- data/lib/active_sanction/country.rb +164 -0
- data/lib/active_sanction/deprecation.rb +168 -0
- data/lib/active_sanction/diff/change.rb +208 -0
- data/lib/active_sanction/diff.rb +348 -0
- data/lib/active_sanction/doctor/checkup.rb +356 -0
- data/lib/active_sanction/doctor/diagnosis.rb +245 -0
- data/lib/active_sanction/doctor/finding.rb +183 -0
- data/lib/active_sanction/doctor/profile.rb +384 -0
- data/lib/active_sanction/doctor/report.rb +227 -0
- data/lib/active_sanction/doctor.rb +347 -0
- data/lib/active_sanction/entity.rb +293 -0
- data/lib/active_sanction/error.rb +332 -0
- data/lib/active_sanction/fetcher/result.rb +116 -0
- data/lib/active_sanction/fetcher.rb +242 -0
- data/lib/active_sanction/http_client/errors.rb +94 -0
- data/lib/active_sanction/http_client/response.rb +129 -0
- data/lib/active_sanction/http_client.rb +324 -0
- data/lib/active_sanction/identifier.rb +212 -0
- data/lib/active_sanction/index/builder.rb +89 -0
- data/lib/active_sanction/index/candidate.rb +63 -0
- data/lib/active_sanction/index/entry.rb +66 -0
- data/lib/active_sanction/index/features.rb +112 -0
- data/lib/active_sanction/index.rb +381 -0
- data/lib/active_sanction/match_result.rb +345 -0
- data/lib/active_sanction/matcher.rb +388 -0
- data/lib/active_sanction/name.rb +183 -0
- data/lib/active_sanction/normalizer/cache.rb +82 -0
- data/lib/active_sanction/normalizer/dictionaries/honorifics.txt +30 -0
- data/lib/active_sanction/normalizer/dictionaries/legal_forms.txt +72 -0
- data/lib/active_sanction/normalizer/dictionaries/organization_stopwords.txt +20 -0
- data/lib/active_sanction/normalizer/dictionaries/particles.txt +35 -0
- data/lib/active_sanction/normalizer/dictionary/stoplist.rb +121 -0
- data/lib/active_sanction/normalizer/dictionary.rb +243 -0
- data/lib/active_sanction/normalizer/form.rb +279 -0
- data/lib/active_sanction/normalizer.rb +137 -0
- data/lib/active_sanction/parsers/column_shape.rb +274 -0
- data/lib/active_sanction/parsers/delimited_table/reader.rb +194 -0
- data/lib/active_sanction/parsers/delimited_table/row.rb +81 -0
- data/lib/active_sanction/parsers/delimited_table.rb +151 -0
- data/lib/active_sanction/parsers/format.rb +107 -0
- data/lib/active_sanction/parsers/join.rb +149 -0
- data/lib/active_sanction/parsers/spreadsheet/archive.rb +258 -0
- data/lib/active_sanction/parsers/spreadsheet/reader.rb +213 -0
- data/lib/active_sanction/parsers/spreadsheet/row.rb +88 -0
- data/lib/active_sanction/parsers/spreadsheet/workbook.rb +314 -0
- data/lib/active_sanction/parsers/spreadsheet.rb +168 -0
- data/lib/active_sanction/parsers/xml_records/backends/nokogiri.rb +134 -0
- data/lib/active_sanction/parsers/xml_records/backends/rexml.rb +157 -0
- data/lib/active_sanction/parsers/xml_records/backends.rb +103 -0
- data/lib/active_sanction/parsers/xml_records/builder.rb +90 -0
- data/lib/active_sanction/parsers/xml_records/reader.rb +134 -0
- data/lib/active_sanction/parsers/xml_records/record.rb +174 -0
- data/lib/active_sanction/parsers/xml_records.rb +140 -0
- data/lib/active_sanction/parsers.rb +94 -0
- data/lib/active_sanction/partial_date/parser.rb +124 -0
- data/lib/active_sanction/partial_date.rb +303 -0
- data/lib/active_sanction/payload_cache/checksum.rb +66 -0
- data/lib/active_sanction/payload_cache/entry.rb +304 -0
- data/lib/active_sanction/payload_cache.rb +366 -0
- data/lib/active_sanction/phonetics/double_metaphone.rb +662 -0
- data/lib/active_sanction/phonetics.rb +40 -0
- data/lib/active_sanction/query.rb +290 -0
- data/lib/active_sanction/rescreen/alert.rb +314 -0
- data/lib/active_sanction/rescreen.rb +332 -0
- data/lib/active_sanction/scorer/adjustments.rb +241 -0
- data/lib/active_sanction/scorer/name_score.rb +279 -0
- data/lib/active_sanction/scorer/reason.rb +136 -0
- data/lib/active_sanction/scorer/result.rb +129 -0
- data/lib/active_sanction/scorer/subject.rb +235 -0
- data/lib/active_sanction/scorer/weights.rb +306 -0
- data/lib/active_sanction/scorer.rb +303 -0
- data/lib/active_sanction/similarity/jaro_winkler.rb +210 -0
- data/lib/active_sanction/similarity/levenshtein.rb +179 -0
- data/lib/active_sanction/similarity/token_set.rb +159 -0
- data/lib/active_sanction/similarity/token_sort.rb +120 -0
- data/lib/active_sanction/similarity.rb +183 -0
- data/lib/active_sanction/snapshot/bundle/header.rb +302 -0
- data/lib/active_sanction/snapshot/bundle/payload.rb +185 -0
- data/lib/active_sanction/snapshot/bundle/signature.rb +201 -0
- data/lib/active_sanction/snapshot/bundle.rb +381 -0
- data/lib/active_sanction/snapshot.rb +305 -0
- data/lib/active_sanction/sources/australia_dfat/published_date.rb +166 -0
- data/lib/active_sanction/sources/australia_dfat/record.rb +319 -0
- data/lib/active_sanction/sources/australia_dfat.rb +291 -0
- data/lib/active_sanction/sources/base.rb +295 -0
- data/lib/active_sanction/sources/canada_sema/record.rb +282 -0
- data/lib/active_sanction/sources/canada_sema/source_ref.rb +89 -0
- data/lib/active_sanction/sources/canada_sema.rb +153 -0
- data/lib/active_sanction/sources/definition.rb +360 -0
- data/lib/active_sanction/sources/eu_fsf/record.rb +467 -0
- data/lib/active_sanction/sources/eu_fsf.rb +203 -0
- data/lib/active_sanction/sources/ofac/record.rb +251 -0
- data/lib/active_sanction/sources/ofac/remarks_parser/coverage.rb +120 -0
- data/lib/active_sanction/sources/ofac/remarks_parser/vocabulary.rb +121 -0
- data/lib/active_sanction/sources/ofac/remarks_parser.rb +312 -0
- data/lib/active_sanction/sources/ofac.rb +248 -0
- data/lib/active_sanction/sources/ofac_consolidated/record.rb +37 -0
- data/lib/active_sanction/sources/ofac_consolidated.rb +203 -0
- data/lib/active_sanction/sources/ofac_sdn.rb +43 -0
- data/lib/active_sanction/sources/remarks.rb +84 -0
- data/lib/active_sanction/sources/uk_sanctions_list/published_date.rb +104 -0
- data/lib/active_sanction/sources/uk_sanctions_list/record.rb +412 -0
- data/lib/active_sanction/sources/uk_sanctions_list.rb +214 -0
- data/lib/active_sanction/sources/un_consolidated/record.rb +279 -0
- data/lib/active_sanction/sources/un_consolidated.rb +140 -0
- data/lib/active_sanction/sources.rb +196 -0
- data/lib/active_sanction/storage/active_record/reader.rb +124 -0
- data/lib/active_sanction/storage/active_record/row.rb +175 -0
- data/lib/active_sanction/storage/active_record/writer.rb +141 -0
- data/lib/active_sanction/storage/active_record.rb +277 -0
- data/lib/active_sanction/storage/base.rb +216 -0
- data/lib/active_sanction/storage/file_system.rb +373 -0
- data/lib/active_sanction/storage/memory.rb +67 -0
- data/lib/active_sanction/storage/meta.rb +188 -0
- data/lib/active_sanction/storage.rb +74 -0
- data/lib/active_sanction/subject.rb +271 -0
- data/lib/active_sanction/sync/report.rb +245 -0
- data/lib/active_sanction/sync/result.rb +296 -0
- data/lib/active_sanction/sync.rb +390 -0
- data/lib/active_sanction/validator_store/file_system.rb +103 -0
- data/lib/active_sanction/validator_store/memory.rb +41 -0
- data/lib/active_sanction/validator_store.rb +119 -0
- data/lib/active_sanction/validators.rb +195 -0
- data/lib/active_sanction/version.rb +23 -0
- data/lib/active_sanction.rb +331 -0
- data/lib/generators/active_sanction/install/install_generator.rb +60 -0
- data/lib/generators/active_sanction/install/templates/create_active_sanction_tables.rb.tt +142 -0
- 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.
|