just_dna_compiler.cli¶
just_dna_compiler.cli ¶
Command-line front door for the reference compiler — a thin Typer shell over the Python API
(validate_spec / compile_module / reverse_module). Lives in just-dna-compiler (never
just-dna-format): the CLI dep (Typer/click) rides with the already-heavy compiler, keeping the
schema package's dependency tier untouched (CONSTITUTION Goal 2).
Exit codes are CI/registry-gateable: 0 success, 1 failure (invalid spec / failed compile).
just-dna-compiler validate spec/
just-dna-compiler compile spec/ out/ --strict --ensembl-cache ref.duckdb
just-dna-compiler reverse parquet_dir/ spec_out/ --version 1.2.3
just-dna-compiler verify out/ --no-require-marketplace --public-key <base64>
just-dna-compiler close spec/ # declare authoring finished (RM73)
just-dna-compiler keygen --out key.pem # the key `sign` needs, and its public half
just-dna-compiler sign out/ --private-key key.pem
just-dna-compiler reference --json # the live authoring reference / JSON Schemas
just-dna-compiler sweep prev/ new/ --spec-root reference_examples/ --release 0.7.0
validate ¶
validate(
spec_dir: Path = typer.Argument(
...,
exists=True,
file_okay=False,
help="Module spec directory",
),
strip_identity: bool = typer.Option(
False,
"--strip-identity",
help="Inject the identity authority keys (namespace/owner/canonical_id) to strip.",
),
authority_key: list[str] = typer.Option(
[],
"--authority-key",
help="Extra authority-owned module key to strip (repeatable).",
),
strict: bool = typer.Option(
False,
"--strict/--best-effort",
help="Pre-flight for a strict compile: escalate the mode-laddered findings to errors, as `compile --strict` does. Use it whenever the compile you intend to run is strict.",
),
) -> None
Validate a spec directory without producing output. Exit 1 if invalid.
Source code in compiler/src/just_dna_compiler/cli.py
compile ¶
compile(
spec_dir: Path = typer.Argument(
...,
exists=True,
file_okay=False,
help="Module spec directory",
),
output_dir: Path = typer.Argument(
...,
file_okay=False,
help="Output dir for parquet + manifest.json",
),
strict: bool = typer.Option(
False,
"--strict/--no-strict",
help="All-or-nothing: fail rather than emit a partial artifact with unresolved positions.",
),
ensembl_cache: Path | None = typer.Option(
None,
"--ensembl-cache",
help="DEPRECATED (removed at 1.0): Ensembl reference (.duckdb/parquet dir); routes to just-dna-enricher. Prefer producing resolution.csv with `just-dna-enricher enrich`.",
),
resolve: bool = typer.Option(
True,
"--resolve/--no-resolve",
help="Resolve missing rsid/position via the injected Ensembl reference.",
),
compression: str = typer.Option(
"zstd",
"--compression",
help="Parquet compression codec.",
),
compiled_by: str | None = typer.Option(
None,
"--compiled-by",
help="Provenance tag for the manifest (e.g. marketplace-server).",
),
strip_identity: bool = typer.Option(
False,
"--strip-identity",
help="Inject the identity authority keys (namespace/owner/canonical_id) to strip.",
),
authority_key: list[str] = typer.Option(
[],
"--authority-key",
help="Extra authority-owned module key to strip (repeatable).",
),
) -> None
Compile a spec directory into a parquet artifact + manifest.json. Exit 1 on failure.
Source code in compiler/src/just_dna_compiler/cli.py
signature ¶
signature(
spec_dir: Path = typer.Argument(
...,
exists=True,
file_okay=False,
help="Module spec directory",
),
) -> None
Print the content signature of a spec's raw authored data — no compile, no Ensembl.
Name- and reference-independent, so a client can compute it and dedup against a registry without recompiling (surviving metadata-strip and a recompile against a different reference).
Source code in compiler/src/just_dna_compiler/cli.py
verify ¶
verify(
module_dir: Path = typer.Argument(
...,
exists=True,
file_okay=False,
help="Compiled module directory (holding manifest.json)",
),
require_marketplace: bool = typer.Option(
True,
"--require-marketplace/--no-require-marketplace",
help="Demand compile_success and compiled_by=marketplace-server. Off for a local artifact.",
),
public_key: str | None = typer.Option(
None,
"--public-key",
help="Base64 raw Ed25519 key the manifest signature MUST verify against.",
),
check_inputs: bool = typer.Option(
False,
"--check-inputs",
help="Also hash the declared inputs[].",
),
check_logs: bool = typer.Option(
False,
"--check-logs",
help="Also hash any logs[] present on disk.",
),
check_provenance: bool = typer.Option(
False,
"--check-provenance",
help="Also hash the provenance document, if declared and present.",
),
check_logo: bool = typer.Option(
False,
"--check-logo",
help="Also hash the logo, if declared.",
),
check_readme: bool = typer.Option(
False,
"--check-readme",
help="Also hash the readme, if declared.",
),
check_derived: bool = typer.Option(
False,
"--check-derived",
help="Also hash any declared derived-fact sidecar CSVs present on disk.",
),
) -> None
Verify a compiled module against its manifest (SPEC §5 verify-then-install). Exit 1 on failure.
The verify-only path the format has always specified and never exposed: verify_manifest and the
signature check live in just-dna-format, which ships no CLI of its own (Typer would breach its
pydantic-plus-cryptography dependency floor), so until now a consumer had to write Python to check
a download. It re-hashes every artifact file, recomputes artifact.digest over the set, and — when
a key is pinned — verifies the Ed25519 signature over that digest.
Source code in compiler/src/just_dna_compiler/cli.py
close ¶
close(
spec_dir: Path = typer.Argument(
...,
exists=True,
file_okay=False,
help="Module spec directory to declare finished",
),
by: str | None = typer.Option(
None,
"--by",
help="Who is closing authoring. Legibility only — sign it to make it provable.",
),
private_key: Path | None = typer.Option(
None,
"--private-key",
exists=True,
dir_okay=False,
help="Ed25519 private key PEM (from `keygen`). Signs the closure over the authored bytes.",
),
) -> None
Declare this module's authoring phase finished, bound to its authored bytes (RM73).
Authoring is a process and it had no end, so every check that needed to know whether a value was
still a copy of its source, or whether a stub had been filled, was guessing. Closing writes a
closure block into the module's verification.json naming the hash of module_spec.yaml and
the authored CSVs as they stand right now. Edit any of them afterwards and the hash moves, the
compiler drops the closure, and the module is open again — which is the point.
It is deliberate on purpose. validate will not do this for you however cleanly it passes: a
record stamped by whatever happened to run says only that something ran. --private-key signs
the act with the same key sign uses on a compiled artifact, which is what turns someone closed
this into this party closed this.
Refuses on a spec that does not validate, and does not refuse on warnings — an unresolved rsID or an ungrounded threshold is a legitimate state to call finished.
Source code in compiler/src/just_dna_compiler/cli.py
sign ¶
sign(
module_dir: Path = typer.Argument(
...,
exists=True,
file_okay=False,
help="Compiled module directory (holding manifest.json)",
),
private_key: Path = typer.Option(
...,
"--private-key",
exists=True,
dir_okay=False,
help="Ed25519 private key PEM.",
),
) -> None
Sign a compiled module's artifact.digest and write the signature into its manifest.json.
Signs the digest, never the files directly: the digest is already a Merkle root over the whole file set, so one signature covers every artifact byte, and re-signing after any edit is impossible to forget — the digest moves and the old signature stops verifying.
Source code in compiler/src/just_dna_compiler/cli.py
keygen ¶
keygen(
out: Path | None = typer.Option(
None,
"--out",
dir_okay=False,
help="Write the private key PEM here (refuses to overwrite). Omit to print it to stdout.",
),
) -> None
Generate an Ed25519 signing key: the private PEM sign needs, and the public key verify pins.
Closes the same gap verify was built to close, one step upstream. verify_manifest and the
signing helpers live in just-dna-format, which ships no CLI of its own (Typer would breach its
pydantic-plus-cryptography dependency floor) — so sign --private-key demanded a file the
toolchain had no way to produce, and verify --public-key demanded a string derivable only by
calling public_key_b64_from_pem from Python. Both halves now have a route.
The key is unencrypted PKCS#8, which is what sign_digest reads. That is a deliberate limit
rather than an oversight: this command bootstraps a key, it is not a key-management system, and
pretending otherwise by adding a passphrase prompt would imply custody guarantees nothing here
provides. A publishing key belongs in whatever secret store the publisher already runs.
Source code in compiler/src/just_dna_compiler/cli.py
reference ¶
reference(
as_json: bool = typer.Option(
True,
"--json/--summary",
help="Full JSON (default), or a one-line-per-table summary.",
),
schemas: bool = typer.Option(
False,
"--schemas",
help="Emit the per-model JSON Schemas instead of the authoring reference.",
),
) -> None
Print the authoring reference — every model's columns, vocabularies and requirements.
Generated from the live pydantic models, so it cannot drift from what the compiler accepts. That
is the point: it is the drift-proof replacement for a hand-kept spec dump, and the consumer that
most needs it (an MCP surface offering an author the valid values) had to import
just_dna_format.reference and write Python, because the schema tier ships no CLI. describe
answers the same question for one table; this answers it for all of them at once, plus the
vocabularies, the open-vs-closed flag, the REQUIRED_ANY_OF rules and the recommended palette.
Source code in compiler/src/just_dna_compiler/cli.py
reverse ¶
reverse(
parquet_dir: Path = typer.Argument(
...,
exists=True,
file_okay=False,
help="Compiled parquet directory",
),
output_dir: Path = typer.Argument(
...,
file_okay=False,
help="Output dir for the reconstructed spec",
),
module_name: str | None = typer.Option(
None,
"--module-name",
help="Override the recovered module name.",
),
title: str | None = typer.Option(None, "--title"),
description: str | None = typer.Option(
None, "--description"
),
report_title: str | None = typer.Option(
None, "--report-title"
),
icon: str = typer.Option("database", "--icon"),
color: str = typer.Option("#6435c9", "--color"),
version: str | None = typer.Option(
None,
"--version",
help="Advisory module.version to re-emit into the spec.",
),
resolution: bool = typer.Option(
True,
"--resolution/--no-resolution",
help="Also emit resolution.csv (the resolved facts), so reverse→compile is fully offline.",
),
genome_build: str | None = typer.Option(
None,
"--genome-build",
help="Override the build. Read from the artifact's manifest.json by default; only needed for a bare parquet directory that carries no manifest.",
),
) -> None
Reverse a compiled parquet artifact back into the authored spec DSL (yaml + csv).
Source code in compiler/src/just_dna_compiler/cli.py
template ¶
template(
kind: str = typer.Argument(
...,
help="Authored CSV to emit a header for, e.g. repeat_alleles.csv",
),
) -> None
Print a header-only CSV for one authored table kind, generated from the live models.
The requirements go to stderr, so just-dna-compiler template x.csv > x.csv stays clean.
Source code in compiler/src/just_dna_compiler/cli.py
stub ¶
stub(
kind: str = typer.Argument(
..., help="Authored CSV to emit stub rows for"
),
rows: int = typer.Option(
1,
"--rows",
min=1,
help="How many stub rows to emit.",
),
) -> None
Print a header plus stub rows, with a placeholder wherever a human must decide.
An unreplaced stub cannot compile — it is refused by name and row — so a half-filled table fails loudly on exactly the rows still to do rather than compiling into a module that asserts nothing.
Source code in compiler/src/just_dna_compiler/cli.py
requirements ¶
requirements(
kind: str = typer.Argument(
..., help="Authored CSV to describe"
),
as_json: bool = typer.Option(
False,
"--json",
help="Emit the machine-readable form.",
),
) -> None
What an author must supply for one table kind: always, one-of, and never-empty defaults.
Source code in compiler/src/just_dna_compiler/cli.py
scaffold ¶
scaffold(
spec_dir: Path = typer.Argument(
...,
file_okay=False,
help="Module spec directory to create",
),
kind: list[str] = typer.Option(
[],
"--kind",
help="Authored table kind to stub (repeatable).",
),
name: str | None = typer.Option(
None,
"--name",
help="Machine name for the module block.",
),
rows: int = typer.Option(
1, "--rows", min=1, help="Stub rows per table."
),
dry_run: bool = typer.Option(
False,
"--dry-run",
help="Report the plan; write nothing.",
),
) -> None
Create module_spec.yaml plus a stub CSV per kind. Never overwrites; existing files are kept.
Re-runnable: run it again with a different --kind to add a table to a module that already exists.
Source code in compiler/src/just_dna_compiler/cli.py
describe ¶
describe(
kind: str = typer.Argument(
...,
help="Authored CSV to describe, e.g. variants.csv",
),
) -> None
Emit the full machine description of one table kind: columns, options, requirements.
Always JSON — this is the form an authoring tool or MCP surface consumes, beside
just_dna_format.reference.authoring_reference().
Source code in compiler/src/just_dna_compiler/cli.py
hint ¶
hint(
kind: str = typer.Argument(
..., help="Authored CSV the rows belong to"
),
rows_file: Path | None = typer.Option(
None,
"--file",
exists=True,
dir_okay=False,
help="Read the CSV text from a file.",
),
row: str | None = typer.Option(
None,
"--row",
help="A single CSV row (or header+rows) inline.",
),
as_json: bool = typer.Option(
False,
"--json",
help="Emit the full machine report.",
),
) -> None
Inspect authored CSV rows and report what is wrong, what the model rewrites, and what is left to you on purpose. Writes nothing — the corrected text goes to stdout for you to use or not.
Offline: this is the pure half. The enricher adds the lookups that need a reference.
Source code in compiler/src/just_dna_compiler/cli.py
sweep ¶
sweep(
before: Path = typer.Argument(
...,
exists=True,
file_okay=False,
help="Compiled output tree from the PREVIOUS release",
),
after: Path = typer.Argument(
...,
file_okay=False,
help="Compiled output tree for THIS release (built when --spec-root)",
),
spec_root: Path | None = typer.Option(
None,
"--spec-root",
exists=True,
file_okay=False,
help="Compile every module spec under this directory into AFTER with the installed compiler.",
),
release: str | None = typer.Option(
None,
"--release",
help="Run the release gate against the ReleaseRecord for this version (`0.7.0`, or the stamped `just-dna-compiler 0.7.0`). Exit 1 on a finding.",
),
as_json: bool = typer.Option(
False,
"--json",
help="Emit the measurement as JSON.",
),
) -> None
Measure what this release changed about compiled output, against a previous release's (RM126).
BEFORE must have been produced by the previous release — one compile per module spec, from the
SAME spec root this run uses, so the compiler is the only variable. With --release the release
gate runs and a measured movement that no ReleaseRecord declares exits 1.
This is a release-sequence command, not an ordinary test: it needs the previous release actually installed. COMPILER.md carries the full sequence.
Source code in compiler/src/just_dna_compiler/cli.py
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 | |