Zero-PHI source-assurance CLI for healthcare's public data inputs
Project description
SourceLock
Every answer comes with a receipt, and your build fails when the source moves.
SourceLock reads the public data healthcare software runs on — CMS coverage
policy, NPPES and PECOS, ICD-10-CM and HCPCS releases, CMS-HCC risk models, the
OIG LEIE — and returns, with every answer, the release it came from, the SHA-256
of the raw bytes behind it, when those bytes were retrieved, and what the answer
does not prove. hc-source lock init pins those sources in a source-lock.json;
hc-source doctor verifies that pin and fails CI the day one of them changes
underneath you.
Install
git clone https://github.com/writtenonwater99/sourcelock
cd sourcelock
pipx install .
Python 3.11 or newer. There is no PyPI release yet, so pip install sourcelock
does not work and this file does not pretend it does — see
RELEASING.md for what publishing takes.
See it work, offline, right now
hc-source valid-on E11.9 2026-07-01
No network, no credentials, no lockfile. The release tables are vendored, so that command answers in under a second on a plane:
{
"code": "E11.9",
"code_nodot": "E119",
"code_system": "ICD-10-CM",
"date": "2026-07-01",
"valid": true,
"description": "Type 2 diabetes mellitus without complications",
"release": "fy2026-april",
"release_effective_from": "2026-04-01",
"release_effective_to": "2026-09-30"
}
followed by the receipt — source, route, release, effective window, retrieval time, upstream status, the hash of the bytes the answer was derived from, the transform version, and whether a mirror answered — and then the part most tools leave out. Four non-claims, printed in full; the first line of each:
does not prove:
- PHI_NOT_EXPECTED: this tool accepts only public typed parameters.
- DOES_NOT_INCLUDE_CPT_DESCRIPTORS: no HCPCS Level I (CPT) record and no ADA
- DOES_NOT_PROVE_COVERAGE: a code being valid for a date of service says
- DOES_NOT_PROVE_CURRENCY: answers come from the release train vendored at
The real output does not wrap and does not trail off: the CPT one runs to a
paragraph naming all seventeen CMS descriptions that quote a CPT number, because
that is what an AMA licence question actually needs. These four are what
codes.valid_on claims it cannot tell you — every one of them, not a
representative sample, and nothing this product does not actually print.
What it is for
If you are building AI or automation over revenue-cycle data, you have run into some version of this:
- A model cited a code set and nobody can say which release. Every answer
here carries
source_version,effective_from/effective_to, andraw_sha256. That is an audit trail, not a log line. - A pipeline broke because CMS renamed something. NPPES retired its V1 bulk
filenames on 2026-03-03; jobs that still generated them got silent 404s.
hc-source lock initpins 23 cheap checks across six sources andhc-source doctorfails the build the day one moves — with the fix, not a stack trace. - An agent will confidently answer from a stale cache. Receipts distinguish
when bytes were retrieved from when upstream last confirmed them, a canary
that read out-of-date data is not allowed to report
ok, and a source that could not be read is never reported as unchanged. - Your reference data is not all public. Sources are plugins. Your licensed AMA CPT tables can be a first-class adapter without forking anything.
- PHI must not end up in a tool call. Parameters are public and typed, no
receipt or log carries a payload, and a guard refuses input shaped like a
patient record. It is a structural best-effort refusal, not a HIPAA control:
hc_source/guard.pystates exactly what it detects and, just as plainly, which evasion classes it does not close.
The six sources
| source | what it answers |
|---|---|
codes |
ICD-10-CM / HCPCS Level II validity on a date of service, release trains |
hcc |
CMS-HCC V24/V28 mapping, hierarchies, community continuing-enrollee scoring |
provider |
NPI Registry lookups, NPPES bulk-file cadence, PECOS enrollment snapshot |
coverage |
CMS coverage policy: NCDs, LCDs and articles, MCD weekly snapshot identity |
leie |
OIG LEIE exclusion checks by NPI, candidate search, monthly refresh status |
demo |
packaged reference adapter used in tests and examples |
codes and hcc answer offline from vendored release data. The rest read live
CMS and OIG endpoints.
Everyday commands
hc-source valid-on E11.9 2026-07-01 # offline
hc-source hcc score --dx E11.9,I50.9 --model v28 --year 2026 --age 72 --sex F
hc-source lookup-npi 1003000126 # NPPES
hc-source check-npi 1003000126 # OIG LEIE screen
hc-source tools # every route, with its parameters
hc-source call coverage.lookup_ncd --param section=30.3 # the general form
hc-source mcp # serve it all to an agent over MCP
call reaches every route, including ones provided by adapters you installed;
the named commands are shorthand for the four questions people arrive with.
Pin your sources and fail the build when they move
hc-source lock init # observe all six sources, write source-lock.json
hc-source doctor # re-check upstream against the lockfile
hc-source init --ci # scaffold the GitHub Actions workflow
Commit source-lock.json and review its diffs like any other lockfile. A green
run ends with:
23 ok, 0 drift, 0 unreachable, 0 schema_changed, 0 unpinned, 0 stale, 0 error
Exit codes are the contract: 0 everything matched, 1 drift or schema change,
2 a source was unreachable or an adapter broke, 3 a canary was observed but
nothing pinned it, 4 everything matched but something reported that what it
read is out of date. 3 and 4 exist because a missing source-lock.json used
to report 23 ok, 0 drift at exit 0 — a build that verified nothing, reporting
green. Full CI setup, per-canary severity, and the on-unreachable /
on-stale policies: docs/ci.md.
Watch it catch drift, in 90 seconds
You do not have to wait for CMS to change something. NPPES really did retire its V1 bulk-file names on 2026-03-03. The repo carries a recording of what the listing page would look like if that class of change happened again:
hc-source lock init
HC_SOURCE_PROVIDER_NPPES_FILES_URL="file://$PWD/tests/fixtures/provider/npi_files_v1_only.html" \
hc-source doctor --source provider
Doctor exits 1, marks provider.nppes_v2_files as drift, and prints:
provider.nppes_v2_files [drift]: NPPES retired the V1 bulk files on 2026-03-03; only
*_V2.zip names are published now (V2 extends the First Name and Legal Business Name
field lengths). The listing this canary just read carries a V1-style name with no _V2
suffix, which means the page has regressed, a stale mirror is being served, or
something upstream renamed the grammar again. Fix: open
https://download.cms.gov/nppes/NPI_Files.html, read the 'Important Information' block
(where CMS announced the V1 retirement), confirm the current version suffix, make sure
nothing in your pipeline still generates V1 filenames
(NPPES_Data_Dissemination_<Month>_<YYYY>.zip silently 404s), then re-pin with
`hc-source lock init`.
Run it again without the override and it goes back to green. That is the whole product: pin what your build depends on, and get told — with an instruction, not a stack trace — the moment upstream moves.
For agents
hc-source mcp serves every discovered tool over MCP stdio: one MCP tool per
route, the same typed parameters, the same receipts, the same PHI refusals as
the CLI. It refuses to start if any adapter failed to import, because an agent
that sees a short tools/list reads it as the whole product.
Responses are cached on disk and revalidated conditionally — an entry is stored
only if the response carried an ETag or a Last-Modified, and a hit is a
request that came back 304, so cached bytes are ones the source confirmed a
moment ago. A cache hit still reports when its bytes were retrieved, with a
cache_hit flag, rather than claiming a fresh read.
How much it helps depends entirely on whether upstream sends validators, and that varies a lot. Measured here against live sources, median of five runs each:
| command | cold | warm |
|---|---|---|
hc-source call leie.check_npi --param npi=… (15.5 MB OIG file) |
2.78s | 0.90s |
hc-source doctor (23 canaries) |
~6s | ~6s (no reliable difference) |
The LEIE file is served with a Last-Modified, so the warm run revalidates it
in one round trip instead of re-downloading 15.5 MB. doctor gets no measurable
benefit: its canaries are cheap by design, so the round trip already dominates
what a cache could save. Every path under api.coverage.cms.gov/v1/ and the
NPPES NPI Registry API sends neither an ETag nor a Last-Modified, so
they are re-fetched every time by design — storing a body with no way to
re-confirm it is exactly the shortcut this product exists not to take.
hc-source cache info lists the endpoints in that state rather than leaving you
to wonder why the entry count is low.
hc-source cache info|clear; HC_SOURCE_CACHE_DIR moves or disables it.
Adding your own source
Three homes, one contract: ship it in the package, publish it as a distribution
advertising the sourcelock.adapters entry point, or drop a file in
./.sourcelock/adapters/ and switch that directory on with
HC_SOURCE_LOCAL_ADAPTERS=1 (off by default: loading a file from it means
executing it, so it is never on where a pull request could add one). A
third-party adapter gets the same validation, the same PHI guard, and the same
receipts — and cannot claim a built-in source id.
ADAPTER_GUIDE.md is the contract; examples/sourcelock-example-adapter/ is a working skeleton with four marked places to change.
More
- ADAPTER_GUIDE.md — writing a source adapter
- docs/ci.md — the GitHub Action, severity, and CI policy
- RELEASING.md — publishing to PyPI
hc_source/guard.py— what the PHI guard detects, and what it does not
License
MIT. See LICENSE. Source data carries its own terms; each adapter
records them in its SourceContract.license_notes.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sourcelock-0.1.0.tar.gz.
File metadata
- Download URL: sourcelock-0.1.0.tar.gz
- Upload date:
- Size: 1.9 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b73d323d0e259d9b1c399643c4d7945440c842ff8b3404addbe1f46e1e6fe276
|
|
| MD5 |
ded20889707f628e5e770bf24c585f77
|
|
| BLAKE2b-256 |
63765e6124b1326a4e5b70a830a644e7db26044591d5fe32d4099a7b60603db8
|
Provenance
The following attestation bundles were made for sourcelock-0.1.0.tar.gz:
Publisher:
release.yml on writtenonwater99/sourcelock
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sourcelock-0.1.0.tar.gz -
Subject digest:
b73d323d0e259d9b1c399643c4d7945440c842ff8b3404addbe1f46e1e6fe276 - Sigstore transparency entry: 2317858696
- Sigstore integration time:
-
Permalink:
writtenonwater99/sourcelock@3e367e3ab9382f828a0480d22cf6b13c6e29e5df -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/writtenonwater99
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3e367e3ab9382f828a0480d22cf6b13c6e29e5df -
Trigger Event:
push
-
Statement type:
File details
Details for the file sourcelock-0.1.0-py3-none-any.whl.
File metadata
- Download URL: sourcelock-0.1.0-py3-none-any.whl
- Upload date:
- Size: 1.7 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8035a9c48d37bf4c96accd26cefb357d891ac03a4862efb29ae22426979373e1
|
|
| MD5 |
1a890fd74a6df8ee077552287a953399
|
|
| BLAKE2b-256 |
b181bc6ed18c7a5a8f1163ecf8de0e64b953bfe4d50e8111174ce0d3ff3739b7
|
Provenance
The following attestation bundles were made for sourcelock-0.1.0-py3-none-any.whl:
Publisher:
release.yml on writtenonwater99/sourcelock
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sourcelock-0.1.0-py3-none-any.whl -
Subject digest:
8035a9c48d37bf4c96accd26cefb357d891ac03a4862efb29ae22426979373e1 - Sigstore transparency entry: 2317858744
- Sigstore integration time:
-
Permalink:
writtenonwater99/sourcelock@3e367e3ab9382f828a0480d22cf6b13c6e29e5df -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/writtenonwater99
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@3e367e3ab9382f828a0480d22cf6b13c6e29e5df -
Trigger Event:
push
-
Statement type: