release: «Мой налог» tax export (v1.27.0) #296

Merged
developer merged 7 commits from development into master 2026-07-28 15:07:27 +00:00
Owner

Promotes the professional-income tax export to production. Merged as #295; development is green including the gated contour deploy (run #1014).

What ships

The direct rail's rouble income is registered with «Мой налог», its receipt is annulled on a refund, and the buyer gets the receipt by email. Decisions D53-D60. Full design in docs/PAYMENTS.md §12.

  • /_gm/mynalog — sign in, run, arm the automatic mode, resolve anything unresolved, CSV fallback with manual receipt-number entry.
  • Three buyer letters (purchase confirmation, fiscal receipt, annulment), all driven by durable queues; no payment path was modified.
  • A daily fiscal-deadline watchdog that runs whether or not the automatic mode is armed.

Pre-flight, verified

  • PROD_MYNALOG_KEY exists, so the rail is live on prod from this deploy rather than dormant. The automatic export is off by default — nothing is filed until the operator ticks it on, after signing in.
  • PROD_MYNALOG_TZ is unset, so Europe/Moscow applies (the taxpayer's zone; its offset decides the tax month).
  • The backend hard-fails on an unresolvable time zone, by design — filing into the wrong tax month silently would be worse. Checked that gcr.io/distroless/static-debian12 ships /usr/share/zoneinfo incl. Europe/Moscow, so the boot is safe.
  • This release carries a schema migration (00019_mynalog.sql), so prod-deploy will add a maintenance window and a consistent pg_dump. The migration is strictly additive — two new tables plus one nullable column — so an image rollback stays DB-safe.

Included incidentally

f8efa21 de-flakes TestNoRepeatWordsHonoursThePinNotTheVariant, which asked for a hint on a randomly drawn rack and failed when the draw had no legal move (reproduced at ~1 run in 300; it turned one CI run red).

After the deploy

Owner-side, in order: sign in at /_gm/mynalog with the INN and cabinet password → make one real purchase → check letter 1 arrives → press Run → verify the receipt in the app and the link in letter 2 → refund from the console → next run annuls it and letter 3 arrives → then arm the automatic mode.

Promotes the professional-income tax export to production. Merged as #295; `development` is green including the gated contour deploy (run #1014). ## What ships The direct rail's rouble income is registered with «Мой налог», its receipt is annulled on a refund, and the buyer gets the receipt by email. Decisions **D53-D60**. Full design in `docs/PAYMENTS.md` §12. - `/_gm/mynalog` — sign in, run, arm the automatic mode, resolve anything unresolved, CSV fallback with manual receipt-number entry. - Three buyer letters (purchase confirmation, fiscal receipt, annulment), all driven by durable queues; **no payment path was modified**. - A daily fiscal-deadline watchdog that runs whether or not the automatic mode is armed. ## Pre-flight, verified - **`PROD_MYNALOG_KEY` exists**, so the rail is live on prod from this deploy rather than dormant. The automatic export is **off by default** — nothing is filed until the operator ticks it on, after signing in. - `PROD_MYNALOG_TZ` is unset, so `Europe/Moscow` applies (the taxpayer's zone; its offset decides the tax month). - The backend hard-fails on an unresolvable time zone, by design — filing into the wrong tax month silently would be worse. Checked that `gcr.io/distroless/static-debian12` ships `/usr/share/zoneinfo` incl. `Europe/Moscow`, so the boot is safe. - **This release carries a schema migration** (`00019_mynalog.sql`), so `prod-deploy` will add a maintenance window and a consistent `pg_dump`. The migration is strictly additive — two new tables plus one nullable column — so an image rollback stays DB-safe. ## Included incidentally `f8efa21` de-flakes `TestNoRepeatWordsHonoursThePinNotTheVariant`, which asked for a hint on a randomly drawn rack and failed when the draw had no legal move (reproduced at ~1 run in 300; it turned one CI run red). ## After the deploy Owner-side, in order: sign in at `/_gm/mynalog` with the INN and cabinet password → make one real purchase → check letter 1 arrives → press Run → verify the receipt in the app and the link in letter 2 → refund from the console → next run annuls it and letter 3 arrives → then arm the automatic mode.
developer added 5 commits 2026-07-28 14:54:49 +00:00
feat(payments): report income to «Мой налог»
CI / changes (pull_request) Successful in 3s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Failing after 24s
CI / ui (pull_request) Successful in 1m17s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Failing after 0s
CI / deploy (pull_request) Has been skipped
e3c2e80a0a
The direct rail runs on НПД, where the provider neither files with the tax
service nor issues a receipt — so nobody was doing it. This registers each
rouble purchase, annuls its receipt on a refund, and hands the buyer the
receipt by email.

Two properties of the (unofficial) lknpd API shape the design. Registering an
income takes no idempotency key, so an error does not mean nothing happened:
the service name is frozen before the call and carries a marker from the tail
of the order id, and after a failure the taxpayer's income list is searched
for that exact name. Found means filed; not found halts the queue for a human,
because declaring an income twice is as wrong as not declaring it. And faults
are classified rather than logged: a token is renewed silently, a throttle
backs off, an outage retries, but three unfixable rejections take the rail out
of service — a changed format must not become thousands of requests overnight.

The console button and the worker share one RunBatch. Automatic mode is armed
from the console, not from configuration, so the operator can watch a run go
through by hand first. A daily watchdog runs whether or not it is armed, since
the case it exists for is the export being off. An idle queue issues no call at
all — not even an authentication.

No payment path changed: the purchase letter rides the existing payment-event
outbox on its own cursor, the receipt and annulment letters ride the export
row. Decisions D53-D60.
test(inttest): de-flake the no-repeat pin guard
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 12s
CI / integration (pull_request) Successful in 21s
CI / ui (pull_request) Successful in 1m16s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m51s
f8efa21158
The bag seed comes from crypto/rand, so an opening rack occasionally has no
playable word — a legitimate engine answer that says nothing about the pin the
test is guarding. Reproduced at roughly one run in three hundred locally, and
it turned CI red once. Retry with fresh games instead: an unlucky draw no
longer decides the result, while a genuinely broken path still fails every
attempt.
fix(payments): stop doubling the pack size in buyer mail
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 23s
CI / ui (pull_request) Successful in 1m16s
CI / conformance (pull_request) Successful in 11s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m50s
4d4452596d
Catalog titles already name the pack ("50 фишек"), so appending the chip count
produced "50 фишек — 50 фишек, 79.00 ₽". The letter now carries the title
exactly as the storefront shows it, then the price.

The tax receipt is unaffected: it states the quantity in the wording the tax
service wants, which is a separate surface with separate requirements.
fix(admin): shorten the receipt letter, show the account id
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 23s
CI / ui (pull_request) Successful in 1m16s
CI / conformance (pull_request) Successful in 10s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m49s
69bddaeb9a
The receipt letter repeated what the receipt itself states. It now carries the
amount, the moment of payment with its offset, and the link — the receipt is
the document, and it opens without authentication, so there is nothing for the
two to disagree about. Subject reworded to name what it is.

The ledger and tax-export tables labelled the account link "card", meaning the
user card. On pages about payments that reads as a bank card, which is exactly
how it was misread. Both now show the first eight characters of the account id,
so the column carries data — two rows from the same buyer are visible at a
glance — while the link still holds the whole id.
Merge pull request 'feat(payments): report direct-rail income to «Мой налог»' (#295) from feature/mynalog-export into development
CI / changes (push) Successful in 3s
CI / unit (push) Successful in 12s
CI / integration (push) Successful in 20s
CI / ui (push) Successful in 1m16s
CI / conformance (push) Successful in 10s
CI / gate (push) Successful in 0s
CI / unit (pull_request) Successful in 12s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Has been skipped
CI / deploy (push) Successful in 1m57s
CI / changes (pull_request) Successful in 3s
CI / integration (pull_request) Successful in 23s
CI / ui (pull_request) Successful in 1m16s
CI / conformance (pull_request) Successful in 11s
1f17e26d9b
owner approved these changes 2026-07-28 14:57:13 +00:00
Dismissed
developer added 2 commits 2026-07-28 14:59:55 +00:00
fix(backend): embed the IANA time-zone database
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 12s
CI / integration (pull_request) Successful in 20s
CI / ui (pull_request) Has been skipped
CI / conformance (pull_request) Successful in 11s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m51s
ca9cc90db9
The taxpayer's zone decides which tax month an income is filed under, and the
config refuses to boot on a zone it cannot resolve rather than misfiling into
UTC. That correctness was resting on the base image happening to ship
/usr/share/zoneinfo — true of distroless/static-debian12 today, but not a thing
to leave implicit on a path where being wrong means a wrong tax return.

Costs about 450 KB of binary. Adds a config test pinning the default zone and
the hard failure on an unknown one.
Merge pull request 'fix(backend): embed the IANA time-zone database' (#297) from feature/embed-tzdata into development
CI / changes (push) Successful in 3s
CI / changes (pull_request) Successful in 2s
CI / unit (push) Successful in 12s
CI / integration (push) Successful in 24s
CI / ui (push) Has been skipped
CI / conformance (push) Successful in 11s
CI / unit (pull_request) Successful in 11s
CI / integration (pull_request) Successful in 21s
CI / ui (pull_request) Successful in 1m16s
CI / conformance (pull_request) Successful in 11s
CI / gate (push) Successful in 0s
CI / deploy (push) Successful in 1m46s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Has been skipped
3a1c1ec825
developer dismissed owner's review 2026-07-28 14:59:55 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

owner approved these changes 2026-07-28 15:07:14 +00:00
developer merged commit f8e6f541be into master 2026-07-28 15:07:27 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: developer/scrabble-game#296