Commit Graph

48 Commits

Author SHA1 Message Date
Александр Зимин aad33c3bf1 chore(ci): propose-image-bump патчит staging values тоже 2026-05-13 12:33:50 +00:00
Zimin A.N. 09e9b80b0c chore(ci): restore nstart tag на default runner constraint
Revert MR !157 (removal). Shared runner pool снова показал queue
contention в часы пик. nstart-tagged выделенный pool обратно — лучше
predictable wait times для CI.

resource_group docker-builds-v2 (для parallel docker builds) остаётся
без изменений — orthogonal к runner tag selection.
2026-05-13 13:28:17 +03:00
Zimin A.N. a0236c38e8 ci: remove nstart tag из default runner constraint
Раньше все jobs pin'ились к runner'ам с tag 'nstart' (выделенный pool).
Runner topology обновлена — все available runner'ы equally provisioned,
сегрегация по тегам больше не нужна.

После remove'а jobs scheduler'ятся на любого available runner'а →
быстрее picks-up в часы пик когда nstart runner был busy. Parallel
docker-* serialize'я по-прежнему работает через resource_group:
docker-builds-v2 (см. .docker_base).
2026-05-13 10:27:54 +03:00
Zimin A.N. 41aa26b7f8 ci(lint): только MR pipeline'ах, не branch-push
Branch-push pipeline сканировал HEAD~10..HEAD range — включал исторические
commit'ы (например b7e1f12 из MR !154 который сам и привёл к появлению
этого lint'а) → false fail на legacy commits которые уже в main.

MR pipeline использует CI_MERGE_REQUEST_DIFF_BASE_SHA..HEAD — только новые
commit'ы данного MR'а. Это правильный gate: contributor opens MR → lint
валидирует только новый код → fail/pass без noise от старого.

Pipeline 6833 (branch ci/forbid-skip-ci-literal) демонстрирует проблему —
поймал historical commit вместо проверки нашего MR-fix'а.
Pipeline 6834 (MR !155) прошёл — это правильное поведение.
2026-05-13 00:58:39 +03:00
Zimin A.N. 1da515276c ci(lint): forbid literal skip-ci marker в commit messages
GitLab платформа skip'ает pipeline на любом commit'е чей subject или body
содержит pattern с square brackets вокруг 'skip ci'. Trap: semantic-release
включает release notes из всех commit'ов в release commit body — если хотя
бы один commit имеет этот pattern в subject/body, release tag pipeline
скипается → нет images → нет auto-PR.

Реальный случай: v2.16.1 — commit fix(release): 'remove ... из semantic-
release commit message' содержал паттерн дословно. Pipeline 6831 (v2.16.1
tag) skipped → пришлось trigger через API.

Новый CI job 'commit-msg-lint' (stage: test):
- Запускается на MR pipeline'ах и feature-branch push'ах
- Скипает chore(release) commit'ы (semantic-release auto-bot)
- Для каждого commit'а в range проверяет subject+body на square-bracket
  skip-ci pattern
- Fails CI с ясной подсказкой (как переформулировать) при match

RELEASE.md обновлён с разделом 'Skip-ci marker trap': примеры
правильных формулировок (skip-ci marker, the directive, etc) и
escape hatch (manual API trigger).
2026-05-13 00:54:19 +03:00
Zimin A.N. cac6b6a981 fix(ci): use printf для commit/MR description avoiding YAML literal block break
Pipeline 6815 failed instantly с 'could not find expected : at line 625'
— multi-line shell strings внутри script: - | литерального блока YAML
ломали parser потому что continuation lines не были indented к уровню
блока (lines 625, 627-629 в column 0 — YAML видел их как new key).

Fix: собрать message строкой через printf '%b' с явными \n. Однострочные
shell variables — YAML парсит без проблем независимо от содержимого
внутри quotes.
2026-05-13 00:24:33 +03:00
Zimin A.N. 02591466cc ci(release): auto-PR в ordinis-infra с новым image tag после release
Раньше release tag создавал v2.X.Y, build images, но values-prod.yaml в
ordinis-infra оставался указывать на старый tag (либо unset → fall to
:latest). Это создавало divergence:

  - Auto deploy через trigger-deploy-prod использовал runtime
    UPSTREAM_IMAGE_TAG = новый tag → правильный deploy.
  - Manual deploy через infra repo (helm upgrade -f values-prod.yaml)
    использовал static values → старый tag → split-brain ноды через
    :latest cache (см. ordinis-infra MR !10 fix).

Новый job 'propose-prod-image-bump':
- Triggers только на tag pipeline (semver v*.*.*).
- Needs все docker-* jobs (images должны быть в registry).
- Clones ordinis-infra, патчит image.tag в values-prod.yaml через awk
  с scope-tracking (только image: block, не writer/readApi/etc).
- Commit + push branch + создаёт MR через GitLab API.
- Reviewer approve → merge → infra pipeline auto-катит deploy-prod.

Git становится единственным источником истины: HEAD ordinis-infra main
всегда отражает что задеплоено на prod.

Setup: новая CI variable INFRA_PROJECT_TOKEN (group-level access token
scope: write_repository, api, role: Developer). Без неё job exits early
с подсказкой — pipeline не блокируется (allow_failure: true).
2026-05-13 00:20:50 +03:00
Александр Зимин 5103ed6d9c fix(admin-ui): changelog build context — copy script + CI before_script 2026-05-12 18:48:04 +00:00
Александр Зимин 6438dd0d7a ci: rename docker resource_group → docker-builds-v2 (stale lock workaround) 2026-05-12 10:32:45 +00:00
Александр Зимин 4a571546d1 ci: docker сборки только на main + tag 2026-05-12 09:54:21 +00:00
Александр Зимин 2b30e60ec9 ci: pin сборки к nstart-tagged runners 2026-05-12 09:49:25 +00:00
Александр Зимин 1f71808acf ci: shorter artifact expire_in — storage relief 2026-05-11 23:21:22 +00:00
Zimin A.N. 9e2d985485 fix(ui): tailwind-merge стрипал text-body — buttons ушли в 16px inherited
CRITICAL bug — на staging buttons рендерились в 16px вместо handoff
workhorse 13px. UpdateBanner secondary buttons показывали invisible
текст (text-ink overrode by inherited text-on-accent от parent banner).

Root cause:
  buttonVariants base = 'rounded-md text-body font-medium ...'
  primary variant     = 'bg-navy text-on-accent ...'

tailwind-merge default config pattern-matches `text-*` как ОДНУ группу
(color). Видя `text-body text-on-accent` он считал это конфликтом двух
colors → keep last → text-on-accent остаётся, text-body стрипается.
Result: button теряет font-size → inherit body 16px.

Fix: extendTailwindMerge регистрирует наши custom semantic typography
utilities (text-body/cell/mono/cap/title-*) в группе font-size (отдельной
от colors). twMerge теперь знает `text-body text-on-accent` это size +
color, обе сохраняются.

Also: text-mono/text-cap в font-family группе (они baked Mono/Tektur)
— fix конфликт с font-mono/font-display tokens если кто-то их случайно
combined.

Verified в preview: 'Sign in' button class теперь содержит text-body,
computed fontSize: 13px (вместо 16px).

Plus CI: release job image node:22-alpine → repo.nstart.cloud/library/
node:22-alpine (Docker Hub rate-limited, mirror используется во всех
других jobs).
2026-05-11 15:45:27 +03:00
Zimin A.N. 1e93d74420 ci(release): fix git auth + fail-fast when GITLAB_TOKEN missing
semantic-release провалился с 'HTTP Basic: Access denied' потому что:

1. CI default git remote использует CI_JOB_TOKEN scope — не может push'ить
   tags/commits в protected refs (main branch).
2. GITLAB_TOKEN ещё не настроен в Settings → CI/CD → Variables.
3. Без explicit git remote rewrite semantic-release/git plugin падает
   с cryptic 'Authentication failed' вместо понятного 'token missing'.

Fixes:
- before_script: fail-fast если GITLAB_TOKEN пустой — печатает setup
  инструкцию (write_repository + api scopes, Masked + Protected flags)
  и exit 0. Pipeline не краснеет (allow_failure: true).
- git remote set-url origin https://gitlab-ci-token:${GITLAB_TOKEN}@... —
  переопределяет default remote чтобы git push мог работать с
  Project Access Token'ом.
- git config user.email/name — semantic-release commit author.

После настройки GITLAB_TOKEN в GitLab UI следующий push в main создаст
первый авто-tag (v1.3.0 minimum по conventional commits с момента v1.2.2).
2026-05-11 15:19:22 +03:00
Zimin A.N. 6805c70a6b ci: semantic-release automation for tag/changelog (Option D)
Закрывает эту тему окончательно. После merge в main CI анализирует
conventional commits с последнего tag'а, бампит package.json + CHANGELOG.md
и пушит git tag vX.Y.Z. Tag pipeline далее собирает images и ждёт
manual gate на trigger-deploy-prod.

Что добавил:
- ordinis-admin-ui/.releaserc.json — config (commit-analyzer, release-notes,
  changelog, npm[no-publish], git, gitlab). branches: ['main']. Маппинг
  conventional-commit types к bump levels.
- ordinis-admin-ui/package.json — devDeps: semantic-release@24.2 + 6 plugins.
- .gitlab-ci.yml — new 'release' stage + release job:
  - needs: trigger-deploy-staging (release ТОЛЬКО после зелёного staging)
  - image: node:22-alpine + pnpm@9.15.4
  - GL_TOKEN из CI variable GITLAB_TOKEN (semantic-release/gitlab требует
    именно GL_TOKEN env name)
  - allow_failure: true (нет релизных commits = OK, не блокирует pipeline)
- RELEASE.md — operator docs:
  - One-time setup: создать Project Access Token + GITLAB_TOKEN CI var
  - Commit conventions table (feat→minor, fix→patch, breaking→major)
  - Skip release (use chore: type или [skip ci])
  - Dry-run command для local validation

Prod safety: trigger-deploy-prod остаётся manual (when: manual). Автоматизация
ТОЛЬКО tag-создания, не deploy. Tag → CI runs → human кликает Deploy.

ACTION REQUIRED от user (one-time):
  1. GitLab → Settings → Access Tokens → создать Project Access Token
     scopes: write_repository + api, role: Maintainer
  2. GitLab → Settings → CI/CD → Variables → GITLAB_TOKEN (Masked, Protected)

Без этого release job будет fail'ить (но pipeline не покраснеет — allow_failure).
2026-05-11 14:51:03 +03:00
Александр Зимин 5a4f04a79b ci: BuildKit cache в docker publish (-10 мин) 2026-05-10 21:56:35 +00:00
Александр Зимин d740092d0c fix(build): pass git info via Docker build args (unblock main pipeline) 2026-05-10 17:10:45 +00:00
Александр Зимин 38a5c19452 ci(docker): serialize docker builds + prune cache 2026-05-10 15:39:26 +00:00
Zimin A.N. 7909588304 ci(docker): add --no-cache to bypass stale inline cache from missing blob
After 44395e2 added --pull, pipeline 6032 still fails on docker-app/
projection-writer/read-api with same blob 74e368... missing. --pull pulled
base image fine, but BuildKit reads BUILDKIT_INLINE_CACHE из previously-pushed
:latest (которое references the lost blob). BuildKit thinks "this blob already
in registry, skip push" → manifest references missing blob.

--no-cache форсирует rebuild from scratch без inline cache reading. Все blobs
пересобираются → pushed заново → manifest is consistent.

Stack +2 min per service (3 services = +6 min total pipeline). Acceptable
до восстановления Nexus blob storage health. Когда :latest references stable
blobs снова — оба флага можно убрать одной revert командой.

Sister fix к 44395e2.
2026-05-10 15:10:46 +03:00
Zimin A.N. 44395e27e4 ci(docker): force --pull on base images to recover from Nexus blob loss
Pipeline 6017 (07c5ca0, main) failed on docker-app/projection-writer/read-api
all referencing missing blob sha256:74e368405305a59e71a1fec5de6a75cc307bd663ebb9b47af045c3a5bdf30cec
после Nexus disk-full cleanup. Blob — это shared layer от base image
(eclipse-temurin:21-jre / maven:3.9.9-eclipse-temurin-21), который Nexus
docker-proxy кэш потерял. BuildKit на runner'е держал stale reference в своём
inline cache, не push'ал blob, manifest fail.

`docker build --pull` форсирует Nexus proxy лениво re-fetch base image с
Docker Hub на каждом build'е → blob возвращается в storage. После одного
успешного pipeline'а можно убрать флаг (cache восстановится), но flag
безвреден в healthy state — стоит ~5-10s на проверку pull.

Не меняет behaviour приложения. CI/runtime fix only.
2026-05-10 14:44:16 +03:00
Zimin A.N. c11044c32e feat(bench): JMH microbenchmarks module + manual CI job
CEO plan v1 polish — закрывает gap "JMH performance benchmarks в CI для
baseline + Redis-enabled scenarios".

New module:
- ordinis-bench/ — JMH 1.37 microbenchmarks. Gated behind `bench` Maven
  profile в parent pom — НЕ строится при обычном `mvn package` (default
  CI остаётся быстрым).
- maven-shade-plugin под `bench` profile собирает uber-jar
  `target/benchmarks.jar` (86 MB, ~12 sec build).
- LineageBenchmark covers hot paths из dict-relationships-v2:
  * parseRef_simple / withSchemaButNoOnClose / withOnClose
  * onCloseAction_block / cascade / null
  * schemaTraversal_findRefs (40 props, 5 refs — realistic shape)

Baseline numbers (M3 MBP, JDK 25, single thread):
- onCloseAction_*: 0.6–12 ns/op
- parseRef_*: 20–93 ns/op
- schemaTraversal: 417 ns/op (~83 ns per ref на 40 props)
Cumulative budget for findSchemaDependents на 40 dicts × 40 props × 5 refs:
~17 µs. p99 endpoint SLO = 200 ms — запас 4 порядка. README documents
budget + 2x regression alarm.

CI integration:
- New job maven-bench (stage: test). Manual trigger only:
  * RUN_BENCH=true variable, OR
  * web pipeline source с manual click.
- Default CI (push/merge) НЕ запускает bench — slow + noisy в history.
- Output artifacts: bench-results.json + bench-results.txt (5 day expire).
- Quick smoke config: 1 fork, 2 warmups, 3 measurements × 2s = ~5 min total.

Side effect: ReferenceValidator.parseRef static methods переведены из
package-private в public. Они и так часть public ParsedRef contract —
visible to bench module без изменения design intent.

Verify:
- mvn -P bench -pl ordinis-bench -am package: BUILD SUCCESS (12s).
- java -jar benchmarks.jar -f 1 -wi 1 -i 2 Lineage: all 7 benchmarks
  exec'ятся, valid ns/op numbers.
- mvn -pl ordinis-rest-api -am test: 106/106 PASS unchanged.
- glab ci lint: clean.

README в ordinis-bench/README.md документирует:
- baseline numbers + 2x regression budget
- local run options (smoke / full / GC profile / JSON output)
- when CI bench runs (manual web trigger)
- difference vs k6/wrk (HTTP load tests are different layer, both needed)
- how to add new benchmark
2026-05-08 12:22:18 +03:00
Zimin A.N. 080ecb7a65 fix(ci): docker-docs всегда собирать на main+tag (drop path-filter)
deploy-staging упал с "context deadline exceeded" → atomic rollback.

Причина: коммит 29f99f0 трогал только .gitlab-ci.yml. По старому правилу
`changes: docs-changes` job docker-docs не запускался → ordinis-docs:main-29f99f05
не пушился. Но resolve-tag всё равно emit'ил DOCS_IMAGE_TAG=main-29f99f05.
Helm пытался pull → ImagePullBackOff → wait timeout → rollback.

Trade-off: docker-docs теперь срабатывает на любой main push. Layer cache
при отсутствии docs/* changes делает rebuild ~30 sec.
2026-05-08 09:51:26 +03:00
Zimin A.N. 29f99f058b fix(ci): пересобирать docker-docs на tag-pipeline + откат main-<sha> hack
Логика:
- maven-package + docker-app/read-api/projection-writer/migrations/admin-ui
  не имеют `rules:` → запускаются на любом pipeline (tag тоже).
  Они пушат "v1-0-X-<sha>" + ":latest" + ":<branch_or_tag>" — образы доступны.
- docker-docs имеет явные rules только на main+changes / web → на tag
  pipeline НЕ срабатывал → docs image "v1-0-X-<sha>" не пушился →
  helm на проде получал ImagePullBackOff.

Фикс:
1. docker-docs: добавил правило `if: $CI_COMMIT_TAG =~ /^vX.Y.Z$/`.
2. resolve-tag: откатил main-<sha> костыль — теперь снова просто
   $IMAGE_TAG, который на tag-pipeline валидно резолвится в v1-0-X-<sha>.
2026-05-08 09:29:45 +03:00
Zimin A.N. 9322f4de56 fix(ci): на tag-pipeline резолвить в main-<sha> (already-built images)
На tag-pipeline (`v1.0.X`) docker-* jobs не запускаются — их rules
матчатся только на main / merge_request / web trigger. Это by design:
тег создаётся уже после успешного main push, образы есть в registry
как `main-<sha>`.

Но resolve-tag раньше выдавал $IMAGE_TAG = "v1-0-5-<sha>" — такого
образа нет → helm pull падал.

Теперь на tag-pipeline резолвим в "main-<sha>" (тот же commit, тот же
SHORT_SHA). На main-pipeline поведение прежнее.
2026-05-08 09:27:57 +03:00
Zimin A.N. 0b1e097c67 fix(ci): resolve-tag должен фирить и на tag-пайплайнах
`trigger-deploy-prod` запускается только на тегах vX.Y.Z и has
`needs: [resolve-tag]`. Но `resolve-tag` имел правило только
`if $CI_COMMIT_BRANCH == 'main'` — на tag-пайплайне CI_COMMIT_BRANCH
пустое, job не появлялся в графе → trigger-deploy-prod ошибка
"resolve-tag does not exist in the pipeline".

Добавил второе правило: `if $CI_COMMIT_TAG =~ /^v[0-9]+...$/`.
Теперь resolve-tag нормально создаёт RESOLVED_IMAGE_TAG / RESOLVED_DOCS_TAG
и для теговых пайплайнов.
2026-05-08 09:26:41 +03:00
Zimin A.N. 27648ee2f7 fix(ci): pnpm via npm install (skip corepack) — Dockerfile + build-docs job
Corepack может hang в CI runner на integrity-check либо non-TTY prompt
при первом activate. Заменяем на прямой npm install с pinned version
9.15.4 (latest stable v9, lockfile compat: lockfileVersion '9.0').

Изменения:
- ordinis-admin-ui/Dockerfile: corepack → npm install -g pnpm@9.15.4
- docs/Dockerfile: same swap
- .gitlab-ci.yml build-docs: same swap

Verified locally: docker build ordinis-admin-ui за 23 sec, no hang.
2026-05-08 00:35:41 +03:00
Zimin A.N. cfe26a07a7 docs: Dockerfile + nginx + CI publish для deploy на ordinis.k8s.264.nstart.cloud/docs/
Multi-stage Dockerfile: yfm build (Node 20 alpine) → nginx 1.27 alpine
serving static HTML. Nginx config:
- /healthz endpoint для k8s probes
- /docs/ alias → /usr/share/nginx/html/ (Diplodoc делает relative links,
  alias корректно работает без URL rewriting)
- Hash-named assets (_bundle/) — cache 1y immutable
- HTML pages — cache 5min must-revalidate
- /docs (без trailing slash) → 301 → /docs/
- Security headers (X-Content-Type-Options, X-Frame-Options, Referrer-Policy)

CI:
- build-docs (sanity check) — теперь только на non-main branches и MR
  (для preview без push). Production build идёт через docker-docs.
- docker-docs (NEW) — multi-stage Dockerfile build + push в repo.nstart.cloud
  через standard .docker_base + .publish_script pattern. Path-filtered на
  docs/**, manual через web UI.
- resolve-tag: emit RESOLVED_DOCS_TAG (= IMAGE_TAG, тот же что для backend).
- trigger-deploy-{staging,prod}: pass DOCS_IMAGE_TAG в downstream pipeline.

Local verify:
  cd docs && docker build -t ordinis-docs:test .
  docker run --rm -p 18088:8080 ordinis-docs:test
  curl localhost:18088/healthz       → 200
  curl -I localhost:18088/docs       → 301 → /docs/
  curl localhost:18088/docs/         → 200 index.html
  curl localhost:18088/docs/integration/api/auth.html → 200
  curl localhost:18088/docs/missing.html → 404

Соответствующие infra-side изменения (Helm chart docs deployment + Ingress
path /docs/, values, deploy script) — commit в ordinis-infra repo.
2026-05-07 23:44:03 +03:00
Zimin A.N. bcfb07f547 docs: split integrator guide (Diplodoc) vs internal docs + add build-docs CI
Чёткое разделение audiences:
- docs/ — consumer-facing only, Diplodoc-built. Public contract для
  интеграторов (Альтум, BI, third-party).
- docs-internal/ — operator runbooks, status snapshots, legacy tech pages.
  НЕ в Diplodoc build, НЕ публикуются.

Изменения:
- Move docs/{ops,status,tech} → docs-internal/{ops,status,tech}
- docs/toc.yaml: убраны секции Operations, Архитектура (legacy), Status
- docs/index.md: убран tab "оператор / on-call", focus на integrators
- docs/README.md: rewritten — scope только integrator guide, editorial
  guidelines (no leak internal architecture, stable API contract)
- .yfm: убран явный ignore status/* (теперь не нужно — папка переехала)
- docs-internal/README.md: index для внутренней документации с rationale
  разделения

Scrubbing implementation details из Diplodoc страниц:
- events.md: убраны Strimzi/cluster.142 references, OutboxPoller детали,
  internal alert names (OrdinisOutboxLagHigh, OrdinisOutboxDLQNonEmpty),
  metric names (nsi_outbox_*). Заменены на consumer-observable contract.
- webhooks.md: убраны metric names (nsi_webhook_ssrf_rejected_total),
  alert table (OrdinisWebhookHighFailureRate и т.д.). Retry policy
  переписана на user-side perspective.
- errors.md: убран alert name OrdinisReadApiErrorBudgetBurnFast, Tempo
  endpoint URL, internal connection details.
- x-references.md: OrphanReferenceScanner → general "Ordinis периодически
  детектирует". Убраны metric/alert names. Cascade roadmap без link на
  internal design doc.
- auth.md: убран file path ScopeContext.java.
- bundle-cuod.md: убран Maven module path.
- caching.md: cascade design link заменён на text reference.

CI:
- .gitlab-ci.yml: новый job build-docs (stage: build) — Node 20 alpine,
  pnpm install --frozen-lockfile + pnpm build, artifact docs/_dist на
  неделю. Triggers: auto на docs/**, либо manual web. Готов к follow-up
  pages-deploy job когда host решён.
- Новый pattern .docs-changes для rules.

Build verified clean: 13 HTML pages, 6.9M, no broken links/refs.
2026-05-07 23:27:03 +03:00
Zimin A.N. cbaf4e6c9a ci(e2e): отдельный maven-e2e job + surefire profile e2e
ordinis-app/pom.xml:
- surefire excludes **/e2e/*E2ETest.java по умолчанию (быстрые unit без Docker)
- profile `e2e`: combine.self override снимает excludes + includes только e2e

.gitlab-ci.yml: новый job maven-e2e:
- maven image + docker:29.1.2-dind service (тот же что docker-* jobs)
- DOCKER_HOST=tcp://docker:2375, TESTCONTAINERS_HOST_OVERRIDE=docker, RYUK_DISABLED=true
- mvn -pl ordinis-app -am -P e2e test
- artifact: junit reports
- rules: backend-changes (как maven-test)

Локально:
  mvn -pl ordinis-app -am test                  # быстро, без Docker
  mvn -pl ordinis-app -am -P e2e test           # с Docker, ~23 sec для 12 тестов

В CI оба job'а параллельно после path-filter. e2e будет ловить регрессии типа
audit_log INET/scope filter gap которые мы уже нашли через эти тесты.
2026-05-05 20:24:44 +03:00
Zimin A.N. 405ddb561b fix(ci): docker-admin-ui всегда rebuild — path-filter создавал ImagePullBackOff
Path-filter (rules: changes: frontend) пропускал docker-admin-ui когда менялся
только backend. Но resolve-tag всегда отдавал общий UPSTREAM_IMAGE_TAG, и
helm в infra тащил admin-ui:<commit>, которого нет в registry → ErrImagePull.

Layer-cache BuildKit делает повторную сборку идентичных слоёв ~30 sec.
Стоимость незначительная, конфигурация надёжнее.

(Backend docker jobs уже собираются всегда — для тех же причин.)
2026-05-04 16:28:21 +03:00
Zimin A.N. f7efb23e09 test+ci: 51 frontend unit-тестов (Vitest) + CI rollback на single resolve-tag
Tests (Vitest):
- src/lib/dates.ts (вынесено из SchemaDrivenForm + dictionaries.$name): parseFormDate,
  formatIsoDate, formatIsoDateTime, extractTime, combineDateTime, localTzOffset, nowIsoLocal
- src/lib/dates.test.ts (20 tests): TZ-safety, pure-date local midnight, far-future filter,
  round-trip parse/format, time extraction в local TZ, clear-button (null Date → '')
- src/components/schema/types.test.ts (31 tests): buildSchemaJson всех 9 kinds + x-unique +
  x-id-source, parseSchemaJson roundtrip, diffSchemas (8 breaking detection cases),
  suggestVersionBump (major/minor/patch + breaking-wins-mix)

CI:
- Откатил dual resolve-tag (BACKEND/FRONTEND) на single resolve-tag.
  GitLab variable interpolation в trigger jobs не подхватывает dotenv от needs-job
  → admin-ui-only push не доезжал до infra. Now backwards compat path работает
  как раньше с UPSTREAM_IMAGE_TAG.
- Backend docker jobs (maven-package + 4 docker-*) теперь без rules:changes —
  catch ImagePullBackOff если frontend push шёл без backend rebuild. BuildKit cache
  делает повторный билд идентичных layers ~30 sec.
- Path-filter оставлен на maven-test (тесты не запускаются на frontend-only PR
  — они бэкендовые) и на docker-admin-ui (frontend-only).

Run: pnpm test → 51 passed.
2026-05-04 04:37:24 +03:00
Zimin A.N. 303a5f0402 feat(ci+ux): path-filtered CI build + Edit semantics в bitemporal model
CI: path-filtering (наконец-то)
- Backend jobs (maven-test, maven-package, docker-app/read-api/projection-writer/migrations,
  resolve-backend-tag) запускаются только при backend-changes (Java модули, pom.xml, CI yml).
- Frontend (docker-admin-ui, resolve-frontend-tag) — только при ordinis-admin-ui/**.
- Trigger downstream передаёт BACKEND_IMAGE_TAG и/или FRONTEND_IMAGE_TAG. Backwards
  compat для старого UPSTREAM_IMAGE_TAG сохранён в infra pipeline.
- Frontend-only PR теперь не катит backend rolling update (raньше каждый push
  тегал ВСЕ сервисы новым sha и роллил).

Edit dialog UX (bitemporal):
- При Edit validFrom default = now() (а не historical validFrom существующей записи).
  Раньше юзер сдвигал validFrom назад → backend не находил активной версии в этот
  момент → 'record_not_active'. Теперь по умолчанию 'новая версия с этого момента'.
- nowIsoLocal() в timezone браузера (с offset).
- validTo default пустой = бессрочно.

Backend: понятное сообщение для record_not_active с инструкцией.
2026-05-04 04:24:17 +03:00
Александр Зимин daad2b6349 ci: docker-admin-ui job (Node build → nginx serving)
Build только при изменениях ordinis-admin-ui/** или каждом push в main.
Использует тот же DinD pattern, push в repo.nstart.cloud/terravault/ordinis-admin-ui.
2026-05-04 01:05:20 +03:00
Александр Зимин 5ed360a979 fix: misc tech debt
- ci: resolve UPSTREAM_IMAGE_TAG via dotenv artifact (resolve-tag job)
  GitLab quirk — $IMAGE_TAG в trigger.variables не интерполируется
  напрямую от upstream global vars; dotenv делает значение job-local,
  тогда interpolation работает.
- projection-writer: management.server.port=8082 + web-application-type=none
  Возврат actuator/health probes — Spring Boot non-web app не имеет
  HTTP сервера; вынесли actuator на отдельный management server.
- docker-compose.yml: локальный dev-стек (PG + Kafka + Redis + 3 services)
  через repo.nstart.cloud/terravault images. Включается профилем 'dev'.
2026-05-03 23:06:24 +03:00
Александр Зимин 075a3c7705 ci: use corp docker-cli-buildx image and dind 29.1.2
Switch to the working pattern provided by infra team:
- image: repo.nstart.cloud/nstart/docker-cli-buildx:1.0.0 (corp-built
  client with proper Harbor credentials baked in / runner-friendly)
- service: docker:29.1.2-dind (matches runner-tested version)
- explicit --tls=false + DOCKER_HOST=tcp://docker:2375 + DOCKER_TLS_CERTDIR=''
The earlier docker:26 + custom TLS attempts hit shared-volume issues
specific to this runner setup.
2026-05-03 22:08:30 +03:00
Александр Зимин 4a3c72e940 ci: use 'extends:' instead of YAML <<: anchor merge
GitLab CI YAML <<: doesn't deep-merge — per-job 'variables:' overwrites
the anchor's variables block, so DOCKER_HOST/DOCKER_TLS_CERTDIR were
lost and the docker client fell back to default tcp://docker:2375 (no
TLS). 'extends:' merges variables key-by-key, so per-job SVC/DOCKERFILE
add to the base instead of replacing it.
2026-05-03 22:00:29 +03:00
Александр Зимин c2ce25094b ci: switch DinD to TLS mode (standard GitLab pattern)
Non-TLS DinD on tcp://docker:2375 had a race where docker login succeeded
but the next command got 'Cannot connect to the Docker daemon'. Switch
to the documented TLS pattern with DOCKER_TLS_CERTDIR=/certs and
DOCKER_HOST=tcp://docker:2376 — both client and DinD share /certs via
the implicit volume.
2026-05-03 21:54:55 +03:00
Александр Зимин 2fb39f7ac5 ci: drop INTERNAL_REGISTRY, push only to repo.nstart.cloud
Single registry for all clusters — staging (161), prod (142), local dev.
- Removed registry.k8s.265.nstart.local (no longer needed)
- Removed --insecure-registry flag from DinD service
- Wait-loop for DinD readiness before docker login (fixes race that
  caused 'Cannot connect to the Docker daemon' on first build command)
- Inlined docker login into the publish_script (kept tied to the build,
  not a separate before_script that ran before DinD was ready)
2026-05-03 21:51:17 +03:00
Александр Зимин 26f2207c74 ci: dual-push images to repo.nstart.cloud (corp Harbor)
Prod cluster (192.168.100.142) cannot reach the local staging registry
registry.k8s.265.nstart.local. The corp Harbor at repo.nstart.cloud is
the shared image source — push there in addition to the staging-internal
registry so both clusters can pull.

- Added EXTERNAL_REGISTRY=repo.nstart.cloud
- before_script does docker login $EXTERNAL_REGISTRY (REGISTRY_USER /
  REGISTRY_PASSWORD already set as group-level CI variables)
- Refactored 4 nearly identical docker-* jobs into a shared
  .dual_push_script anchor; per-job vars (SVC, DOCKERFILE, BUILD_CONTEXT)
- Build once with both registry tags, push to both
2026-05-03 21:45:06 +03:00
Zimin A.N. e537888554 ci: trigger-deploy-staging вместо -dev (dev перешёл в docker-compose) 2026-05-03 19:05:26 +03:00
Zimin A.N. 5b686abaa8 ci: --insecure-registry для DinD push в registry.k8s.265.nstart.local
Internal registry имеет cert валидный для ingress.local, не для своего DNS name.
Push HTTP-only через insecure-registry flag.
2026-05-03 18:44:55 +03:00
Zimin A.N. 8eb0ec086e ci: pull base images через repo.nstart.cloud (DH official → /library/, vendor → /<vendor>/) 2026-05-03 18:40:29 +03:00
Zimin A.N. fe403f2dbe ci: default retry до 2 раз на mirror failures 2026-05-03 18:19:58 +03:00
Zimin A.N. 00c905401f ci: pull_policy if-not-present (mirror flaky на always) 2026-05-03 18:18:02 +03:00
Zimin A.N. 9a79b53eb0 ci: align с altum pattern — runner mirror'ит Docker Hub
- Убраны префиксы repo.nstart.cloud/ — runner config (DOCKER_AUTH_CONFIG)
  обрабатывает pull из Docker Hub mirror автоматически
- DinD сервис: alias=docker, --tls=false, DOCKER_HOST=tcp://docker:2375
  (proven pattern из altum/.gitlab/ci/docker.yml)
- IMAGE_TAG = ref-slug + sha (как у altum)
- BUILDKIT_INLINE_CACHE=1 для cache-from optimization
- Dockerfiles: FROM maven/eclipse-temurin/liquibase без префиксов
2026-05-03 18:16:21 +03:00
Zimin A.N. 7c06cf7f71 ci: pull images через repo.nstart.cloud (corp Docker Hub mirror)
Runners в кластере не имеют network access к Docker Hub.
Заменили все base images:
- maven:3.9.9-eclipse-temurin-21 → repo.nstart.cloud/maven:...
- eclipse-temurin:21-jre → repo.nstart.cloud/eclipse-temurin:21-jre
- liquibase/liquibase:4.27.0 → repo.nstart.cloud/liquibase/...
- docker:27 / docker:27-dind → repo.nstart.cloud/docker:...
2026-05-03 18:02:46 +03:00
Zimin A.N. aa26724661 fix(migrations): preConditions ДО comment в changelog (XSD), relative include paths
- 0001-extensions.xml: переставил <preConditions> перед <comment> (XSD требует
  preConditions первым в changeSet)
- master.xml: include paths сделаны relative к самому master.xml (changes/...
  вместо db/changelog/changes/...) — CWD inside Docker image это /liquibase,
  Liquibase searchPath = /liquibase/changelog где master.xml лежит, includes
  должны идти от него

CI: insecure-registry: registry.k8s.265.nstart.local (HTTP без auth для
internal cluster), REGISTRY_USER/PASSWORD optional (skip docker login если
не заданы)
2026-05-03 17:54:30 +03:00
Zimin A.N. bc65728d1b feat(ci): GitLab CI pipeline для build + push images
stages:
- test: mvn verify (unit + integration tests когда появятся)
- build: mvn package для всех 4 jar'ов с кэшированием Maven repo
- publish: docker build + push 4 images в registry.k8s.265.nstart.local
  с тегами {short-sha, branch-slug}. Параллельно через 4 jobs.
- trigger-deploy: запускает downstream pipeline в terravault/ordinis-infra
  с UPSTREAM_IMAGE_TAG, branch=main → dev (auto), tag=v*-rc → staging (auto),
  tag=v* (final) → prod (manual approve)

Required GitLab CI variables:
  REGISTRY_USER, REGISTRY_PASSWORD — для docker login в private registry
2026-05-03 16:12:30 +03:00