Yoseph Eikelman
← Work

My own product · Sep 2025 to now

ClinicLog: built by one engineer, registered with the Israel Tax Authority

diary ledger entries the 16 October backfill wrote for 11 Israeli workspaces
7,868
obligations tracked in the repository, from privacy law to the tax registration; 36 tied to the files they depend on
53
of downtime to move Israel's production to Tel Aviv, every table count matched
9 min
cap on how long the processing cache keeps a plaintext transcript; the service will not start with a longer one
24 h

ClinicLog is the practice software I build for independent therapists: diary, clients, payments, and session notes. It has been live in Israel since October 2025, on the web and now in both app stores, and on the web in the US since March 2026. I am its only engineer, and I build it with AI coding agents.11Code history, 1 Nov 2026: 5,344 commits since 30 Sep 2025 by one engineer; 97% from Jan to Jun 2026 carry an AI co-author line; US live 18 Mar; pilot 11 Oct A clinical psychologist, now my co-founder, has used it daily in her own practice since March 2026, and a pilot with other therapists opened on 11 October.

A therapist’s records are health data, and in Israel the diary is also a tax record. A small product cannot hire a compliance team, and it cannot ship features that quietly break a promise made to a regulator. So the rules the product has to meet live in the same repository as the code, next to the files they depend on and, for nine of them, the tests that check them.

What one engineer runs

All of this is mine to build and to keep running:22Deploy history, 2 Oct to 1 Nov 2026: 222 successful deploys of 287; 30 alert rules checked in CI; restores nightly in Israel, monthly in the US; 21 containers a region

  • The product: one web app in Hebrew and English, with iOS and Android apps around it.
  • The back end: an API and two background workers on Postgres, a cache and object storage, 21 containers in each region.
  • Shipping: every push to main runs both test suites. Since 9 October a deploy waits for its commit’s tests to pass, ships that one commit to one region or both, and rolls back on failure. When GitHub could not start its test runners for part of October, I shipped through a logged override, which still refuses a commit whose tests ran and failed. 222 successful deploys between 2 October and 1 November.
  • Watching it: 30 alert rules checked on every push to main, dashboards, error tracking, a daily health check that emails only when something is wrong, and a weekly report.
  • Backups: encrypted daily, weekly and monthly, Israel’s kept in Israel. In Israel every nightly backup is restored into a scratch database before it is uploaded; in the US the newest offsite backup is restored once a month.
  • Security: field-level encryption with versioned keys, two-step sign-in required in Israel, an audit log, and a virus scan on every file stored with a client’s record.

The model writes a draft. The therapist signs.

In Israel, a therapist can record a session once the client has consented to recording. The audio becomes a transcript, and a model then drafts the note in the therapist’s own template, from the transcript and never from the audio.

The model’s note never becomes final on its own: it goes onto the record as a draft, or is appended to a note the therapist has not finalised. Making it final is the therapist’s own action, and once a note is final, the recording pipeline refuses to write over it. Until the therapist reviews it, the plaintext transcript and draft wait in a processing cache. A setting caps how long the cache keeps them at 24 hours, and the service will not start with a larger value. A compliance test holds the nine cache lifetimes that keep client text to that limit, and an hourly sweep files an unreviewed draft into the record, still marked as a draft, when the 18-hour review window closes, before the cache drops it. A transcript the therapist approves is kept on the record, encrypted; the 24 hours govern the cache.33Code and 68 tests: the pipeline never finalises a note, the therapist does; the 24 h cache cap is set in config and checked by a test; review sweep every hour

A diary the Tax Authority registered

Israeli bookkeeping rules treat a clinic’s diary of treatments as a book: numbered without gaps, never edited, kept for years. ClinicLog was registered as diary software on 31 October.

The ledger is append-only in the database itself. Triggers refuse updates, deletes and truncation, and the application’s database role can only read and insert. Each workspace gets one numbering per tax year. An insert locks the counter, so a rolled-back insert gives its number back and the numbers have no gaps. Each entry carries a hash over the previous entry’s hash, so removing or reordering a row breaks the chain. 194 tests cover the ledger.44Database rules and 194 tests: entries cannot be changed or deleted; built 15 Oct 2026; 7,868 entries for 11 workspaces on 16 Oct; registered 31 Oct, valid to Oct 2028

The design document states the limit, and so do I: the chain catches a row edited or deleted in place. It does not stop someone who can insert as the application, who holds the key, or who has full control of the database: the chain hash itself is not keyed, so such a person could delete a row and recompute every hash after it. Only a chain hash kept outside the database, such as an earlier export, would show that. It is not a signature.

The law, next to the code

Compliance register
The controls are ordinary tests, so a broken one fails the normal test run.

One file in the repository lists 53 obligations: privacy regulations, the privacy impact assessment, the tax registration, vendor agreements, security reviews. Each names its basis, an owner and a due date. 36 of them also list the files they depend on, and 9 name the tests that check them.55Obligations register, 1 Nov 2026: 53 obligations, 36 tied to files, 9 to tests; checked on every push and daily; 45 issues opened since 10 Oct

On every push to main, CI compares the changed files with those lists. A change to the recording code, for example, puts the privacy impact assessment on review. That does not block the push. It adds the obligation to one open review issue and sends me an alert, because the answer is often “still true, because”. The register check itself turns the run red, among other reasons when a watched path no longer matches any file or a named test no longer exists:

for pattern in ob.watches:
    rx = glob_regex(pattern)
    if not any(rx.match(p) for p in tracked):
        errors.append(f"{where} watch {pattern!r} matches no tracked file")
for path in ob.controls:
    if not (ROOT / path).is_file():
        errors.append(f"{where} control {path} does not exist")

Without that check, renaming a file would turn a watch off silently.

Four calls, and what I turned down

  • One codebase for two countries. A fork per country, or region checks spread through the code, were the obvious options. The differences, from payment methods to the tax diary, are flags in one file that a generator turns into typed Python and TypeScript, so a fix reaches both countries in one deploy. There are 52 flags today.
  • Store apps that update with the web. Installed apps used to stay on old builds until people updated them by hand. Each deploy now builds a new web bundle that the app switches to at its next cold start, never in the middle of a recording, and a bundle that fails to start rolls back. The first store build with it went up on 19 October; from that build on, a web fix ships to phones in the same deploy.
  • One door for client text sent to AI. Features used to build their own AI clients. Now one module builds every AI client, and a test fails if any other module tries. Inside a feature’s scope, other people’s names, phone numbers and ID numbers are masked before a prompt leaves. The client’s own name stays, because models turn a placeholder into an invented name. Each of the 21 places that call a model is listed with what it sends, and, where nothing is masked, why.
  • A backup counts when it restores. Every freshness check was green. The first real restore drill, on 26 September, found that Israel’s dump would not restore cleanly. Now Israel restores every nightly backup before uploading it, the US restores its newest offsite backup every month, and the health check alerts if either fails or goes missing.66Code and design records: 52 region flags in one file; first store build with live updates 19 Oct 2026; 21 AI call sites behind one gateway; first restore drill 26 Sep 2026

Moving a country’s data in nine minutes

To register, the books had to be in Israel, backups included, so Israeli production moved from a European server to Google Cloud in Tel Aviv on 14 October. I rehearsed the move on an empty database first. On the day, I stopped the application and saved a fingerprint of the database: the row count of each of its 78 tables and a hash of its schema. I streamed the copy and started the new database on its own. It went live only after its fingerprint matched; otherwise the plan was to roll back. The site was down for about nine minutes, and the 13 data volumes matched by checksum.77Migration log, 14 Oct 2026: down from 17:14 to 17:23 UTC; 78 table counts and the schema matched; 13 data volumes matched by checksum

A failure, and what it changed

On 20 October a deploy restarted a worker in the middle of transcribing a 58-minute pilot session, and the session was lost. Our copy of the audio was deleted as soon as the speech service accepted it, the worker deleted the service’s copy on its way out, and the transcript lived only in the worker’s memory, so there was nothing to retry from. I kept the early deletion and made the processing resumable instead. How that works.

What transfers

  • Put the rules where the changes happen. A list of obligations with the files they depend on turns “did this change break a promise?” into an issue that opens itself.
  • Make the model’s output a draft by construction. The final note is a person’s action, enforced in code, not a checkbox in the interface.
  • Enforce retention where it is set. A setting the service refuses to start above is stronger than a policy document.
  • Prove a migration with numbers saved before it. Counts and a schema hash taken beforehand make “it looks fine” a pass or a fail.

Sources

7 sources behind the numbers above
  1. 1Code history, 1 Nov 2026: 5,344 commits since 30 Sep 2025 by one engineer; 97% from Jan to Jun 2026 carry an AI co-author line; US live 18 Mar; pilot 11 Oct
  2. 2Deploy history, 2 Oct to 1 Nov 2026: 222 successful deploys of 287; 30 alert rules checked in CI; restores nightly in Israel, monthly in the US; 21 containers a region
  3. 3Code and 68 tests: the pipeline never finalises a note, the therapist does; the 24 h cache cap is set in config and checked by a test; review sweep every hour
  4. 4Database rules and 194 tests: entries cannot be changed or deleted; built 15 Oct 2026; 7,868 entries for 11 workspaces on 16 Oct; registered 31 Oct, valid to Oct 2028
  5. 5Obligations register, 1 Nov 2026: 53 obligations, 36 tied to files, 9 to tests; checked on every push and daily; 45 issues opened since 10 Oct
  6. 6Code and design records: 52 region flags in one file; first store build with live updates 19 Oct 2026; 21 AI call sites behind one gateway; first restore drill 26 Sep 2026
  7. 7Migration log, 14 Oct 2026: down from 17:14 to 17:23 UTC; 78 table counts and the schema matched; 13 data volumes matched by checksum