A3 monorepo: specification and cross-language implementations of the Amino Acid Annotation format
The spec/ directory holds the language-agnostic conformance contract that all
five implementations are held to:
spec/error-codes.md: the rule registry — one stable code per validation rule, with its stage and the path it reportsspec/error-paths.md: the path grammar — every issue carries an RFC 6901 JSON Pointer to the offending valuespec/conformance/: the shared conformance corpus, read by all five implementations from one copy
Conformance is exact, not approximate. Given the same document, all five
implementations report the same set of {code, path} pairs from the same
validation stage — codes and JSON Pointer paths are contractual, message wording
is not. Every implementation is run against the one shared corpus in CI on every
change, so a divergence fails the build rather than surfacing later as a
cross-language bug.
r/:rtemis.a3python/:rtemis.a3julia/:RtemisA3typescript/:@rtemis/a3rust/:rtemis_a3
The five APIs are idiomatic per language but structurally identical: the same operations, the same arity, the same return shapes. A reader moving between languages should not have to guess.
| Operation | R | Python | TypeScript | Julia | Rust |
|---|---|---|---|---|---|
| Build from parts | create_A3() |
create_a3() |
new A3() |
create_a3() |
— |
| Parse JSON text | read_A3json() |
a3_from_json() |
A3.fromJSONText() |
a3_from_json() |
a3_from_json() |
| Validate, throwing | read_A3json() |
a3_from_json() |
new A3() |
A3() |
validate() |
| Validate, not throwing | validate_A3json() |
validate_a3() |
validate() |
validate() |
validate() |
| Read a file | read_A3json() |
read_a3json() |
readJSON() |
read_a3json() |
(a3 CLI) |
| Write a file | write_A3json() |
write_a3json() |
writeJSON() |
write_a3json() |
(a3 CLI) |
| Serialize to JSON | write_A3json() |
a3_to_json() |
toJSONString() |
a3_to_json() |
a3_to_json() |
| Residue at a position | aa_sub() helpers |
residue_at() |
residueAt() |
residue_at() |
residue_at() |
| Variants at a position | x@annotations@variant |
variants_at() |
variantsAt() |
variants_at() |
variants_at() |
| Issue list on failure | condition issues |
err.issues |
err.issues |
err.issues |
err.issues() |
| Issue stage | issues[["stage"]] |
err.stage |
err.stage |
stage(err) |
err.stage() |
Rust has no file helpers because the a3 CLI covers that use. R's non-throwing
entry point takes a path rather than a parsed value, because R's parse step
needs specific jsonlite settings that a caller should not have to know.
Validation is collect-all: a document is checked in four stages, every issue in the earliest failing stage is reported at once, and each issue carries a stable code and an RFC 6901 JSON Pointer. Codes and paths are identical across all five implementations; messages are not, and may be reworded freely.
[A3E_POS_OUT_OF_BOUNDS] /annotations/site/Active site/index/2:
position 99 is out of bounds for a sequence of length 24 (must be 1-24)
Reading a document collects every issue; calling a constructor fails
fast with one corrective message. That split is deliberate — see
spec/README.md.
Recipes are in the justfile; just --list shows them all.
just test |
every suite in all five, reporting all five even when one fails |
just conformance |
the shared corpus against all five |
just corpus |
regenerate spec/conformance/ from its generator |
just check |
everything CI runs |
Changing validation? Read spec/README.md first — codes and
paths are contractual, and a rule must land in all five at once.
Releases are manual and unautomated; the five packages carry different version numbers but ship from one commit, tagged once for the Zenodo DOI.
The R package is licensed GPL (>= 3); it imports rtemis.core, which is
itself GPL-licensed. The Python, TypeScript, Julia, and Rust packages are
licensed MPL-2.0. This split is deliberate, not an oversight.
rtemislive-draw provides support for interactive visualization of A3 data using the @rtemis/a3 TypeScript implementation.

