Skip to content

Releases: diagnos-tech/integration

Release list

diagnos 0.1.0

Choose a tag to compare

@github-actions github-actions released this 13 Oct 15:36
a7d62e9

0.1.0 is the first version of diagnos ever published — see
MIGRATING.md if you are coming from an internal or from-source checkout of the former imgexam
monorepo.

Added

  • One package, diagnos, with extras. pip install diagnos is the base: no dependencies at all, and four
    commands — diagnos docs (Help Center, Developer Central, Trust Center), diagnos version (version, interpreter,
    platform and installed extras, --json too), diagnos setup and diagnos help. diagnos[sdk] adds the SDK
    (from diagnos import Diagnos), diagnos[cli] the full command line, diagnos[api] the REST API (diagnos-api);
    openbao and dev-certs sit on top. import diagnos works on the base install, and a missing extra raises
    MissingExtraError naming the command that adds it. diagnos-cli and diagnos-api are no longer separate
    packages; MIGRATING.md maps the old names.

  • diagnos setup sdk | cli | api: installs one layer step by step — the SDK into your project with uv add, the CLI
    into the environment that runs diagnos (or as a uv tool), the API as its signed image with Docker. Every
    command is shown first and confirmed once (default no); --dry-run and --yes for scripts. The API image is
    verified by the pulled digest with cosign (3.1.3+) against the release workflow's identity, and the docker run is
    printed by that digest. A missing uv or Docker exits with 69 and its install page — nothing is ever installed
    behind your back.

  • wrappers/contract.json: the language-neutral contract of the four base commands, for wrappers in other
    languages; the Python base is tested against it.

  • Signed releases: every wheel, the sdist and SHA256SUMS carry a cosign keyless signature and a SLSA provenance
    attestation; PyPI gets PEP 740 attestations through Trusted Publishing; the GitHub release attaches every file,
    bundle and the commands that verify them (SECURITY.md).

  • Images on GHCR, for linux/amd64 and linux/arm64: ghcr.io/diagnos-tech/diagnos-api and
    ghcr.io/diagnos-tech/diagnos (the CLI), built from the released wheels, with SBOM and provenance, signed by
    digest. They run as uid 10001 with root-owned, read-only code and no pip or uv.

  • Renamed the project from imgexam to diagnos, end to end: packages (imgexam → diagnos, imgexam-cli →
    diagnos-cli, imgexam-api → diagnos-api), modules (imgexam → diagnos, imgexam_cli → diagnos_cli,
    imgexam_api → diagnos_api), the public class (Imgexam → Diagnos) and exceptions (ImgexamError →
    DiagnosError, ImgexamPermissionError → DiagnosPermissionError), every IMGEXAM_* environment variable to
    DIAGNOS_*, the CLI command (imgexam → diagnos), the Docker image (imgexam-api → diagnos-api), and the
    default vault URL to https://vault.diagnos.health. See MIGRATING.md for the full list,
    including what was deliberately left unrenamed (the imgexam-*-v1 cryptographic labels and the OpenBao static
    seal key id).

  • Consumer-driven Pact contract test suite (contracts/) between the SDK and the vault: make contract /
    make contract-check, a deterministic, normalized contracts/diagnos-sdk-diagnos-vault.json, and provider states
    covering the clock, enrollment (pending, approved, denied, forgotten), lock, patients, exams and files. See
    contracts/README.md.

  • Makefile as the single entry point for every check a contributor or CI runs (sync, fmt, lint, types,
    test, cov, contract, contract-check, check).

  • GitHub Actions: CI on every pull request (lint, mypy --strict, the docs, the Pact contract, the Rust enclave,
    every Python suite on CPython 3.11 and 3.13 with a coverage floor, and zizmor and actionlint over the workflows),
    behind one required check; Package on pull requests into main; Release on v* tags on main; monthly
    grouped Dependabot updates into develop, whose minor and patch updates merge on their own once CI passes; and a
    weekly OpenSSF Scorecard.

  • A self-hosted coverage badge, published to the orphan badges branch by CI on every release.

  • Bilingual (🇺🇸/🇧🇷) documentation across the repository, enforced by scripts/check_docs.py (every document ships
    as NAME.md + NAME.pt-BR.md) and scripts/check_bilingual.py (every module, class and public function's
    docstring, Python and Rust).

  • Repository layout: the three packages live under apps/ (apps/sdk, apps/cli, apps/api); contracts/,
    docs/ and scripts/ stay at the root.

  • update(..., expected_latest_version_id=...) (CLI --expect-version): the vault refuses the write if another
    version was saved since. Reservations that meet another writer's pending version are retried briefly (1.5 s, 3 s),
    and commits lost to the network or a 5xx are replayed with backoff.

  • Patient tags (sealed list labels): create(..., tags=[...]), update(..., tags=...) (None keeps them), CLI
    --tag.

  • DIAGNOS_TIME_PRECISION / Settings.time_precision: truncate dates to the workspace's anonymization precision
    before sealing, as the web app does; diagnos.truncate_timestamp and diagnos.to_iso_instant for your own dates.

  • ProtocolError for a vault answer the protocol does not allow (e.g. a signed size that is not the body's); the API
    answers it with 502 and code: protocol_error.

  • Folders: Drive.create_folder(), and parent_id= on upload, upload_many, list and iter_all (CLI
    files mkdir and --folder; API POST /v1/drives/{sg}/folders and parent_id).

  • Workspace-wide files: vault.drives.list()/iter_all() across every group the session may list, filtered by
    security_group, exam_id, parent_id and include_pending; vault.drives.get(), name_of(), download() and
    iter_download() read any file by node id alone (CLI files list without --group, files get/download by id).

  • DIAGNOS_STORAGE_HOSTS / Settings.storage_hosts: presigned storage URLs are followed only over HTTPS to an
    allowed host or its subdomains — by default the user-content domain diagnosusercontent.com and R2's
    r2.cloudflarestorage.com — so ciphertext and SSE-C keys never go to a host nobody chose.

  • CLI: --group is optional on patients create, exams create, files upload and files mkdir — it falls back to
    DIAGNOS_GROUP, then (for an exam) the patient's group, then the session's only group, and says which it picked;
    with several groups it stops and lists them.

  • Patients.index(id) / Exams.index(id): a document's metadata (group, versions, flags) without opening its record.

  • CLI session agent: diagnos login keeps the session for the commands that follow, until diagnos logout, the idle
    timeout (DIAGNOS_AGENT_IDLE_MINUTES, 8 hours by default) or SIGTERM. The session stays in one background
    process, in the SDK's locked memory; later commands send it their arguments over a private Unix socket and it runs
    them, streaming output, prompts and exit codes back — the keys never leave that process, nothing is written to disk
    and no OS keychain is used. DIAGNOS_AGENT=off keeps every command in its own process. diagnos status shows the
    agent.

  • CLI diagnos logout: revokes the kept session, wipes its keys and stops the agent.

  • CLI global options (--json, --quiet, --no-color, --token, --vault-url) go anywhere on the line:
    diagnos patients list --json works.

  • API diagnos-api dev-certs [DIR]: a throwaway local CA, a localhost server pair and a client pair in one step
    (keys 0600, 30 days, never overwritten without --force; needs the diagnos[dev-certs] extra outside a
    checkout). python -m diagnos.api runs the API like diagnos-api.

  • diagnos.UploadSource, exported at the top level. A .dcm file is typed application/dicom, so the vault
    classifies it.

Changed

  • Patients and exams speak the vault's current protocol (vault.patients, vault.exams, and the matching CLI
    commands and API routes). A document belongs to exactly one security group (security_group= takes one id;
    DocumentIndex.security_group_id); version state lives in DocumentIndex.streams (with latest_version_id,
    versions and pending_version_id shorthands for the data stream); patients' version routes carry
    /streams/data; every version is sealed under a per-version key derived from the vault's security_context, as a
    raw salt ‖ nonce ‖ ciphertext body; every write carries the sealed summary (encrypted_index). Pinned by vectors
    sealed with the web app's own code and by 13 new contract interactions.
  • PatientRecord and ExamRecord now mirror the web app's records: identifiers (vault-sealed), address,
    race_identity; report_lexical/report_html, modality, exam_date. legal_id, ExamRecord.description and
    ExamRecord.report are gone. Dates are stored the way the web app stores them (UTC ISO 8601 instants).
  • A misspelled record field (birthdate) is refused with the field it resembles; any other unknown field is kept, so
    a read-modify-write never drops a field the web app added. Validation errors on records never echo the value.
  • list() returns rows with the decrypted summary (PatientListItem/ExamListItem); the CLI shows it with
    --summary and the API with ?summary=true — rows stay anonymous by default.
  • get() returns the newest content like the web app (a newer draft wins); include_draft=False / --committed
    read committed versions only.
  • Archive and delete no longer re-upload the record: they are flag changes without a new version. New restore()
    (CLI restore, API POST …/restore) takes a document out of the trash.
  • Exams.create no longer takes modality=: modality is a sealed record field; only patient_id is sent in clear.
  • Files speak the vault's current protocol (`...
Read more