Commit Graph

782 Commits

Author SHA1 Message Date
Александр Зимин 0918820661 fix(admin-ui): Checkbox indeterminate stale state 2026-05-10 18:53:07 +00:00
Александр Зимин 67c90efd9e Merge branch 'fix/version-endpoint-permitall' into 'main'
fix(auth): permitAll /api/v1/version (UI polling работает до login)

See merge request 2-6/2-6-4/terravault/ordinis!21
2026-05-10 18:48:55 +00:00
Александр Зимин b428c78b18 fix(auth): permitAll /api/v1/version (UI polling работает до login) 2026-05-10 18:48:55 +00:00
Александр Зимин deb61a1dea Merge branch 'feat/catalog-hub-view' into 'main'
feat(admin-ui): Hub view (B) per design handoff — dict relations 3-col layout

See merge request 2-6/2-6-4/terravault/ordinis!20
2026-05-10 18:41:37 +00:00
Александр Зимин 1df232679d feat(admin-ui): Hub view (B) per design handoff — dict relations 3-col layout 2026-05-10 18:41:37 +00:00
Александр Зимин 6b71b651e6 Merge branch 'fix/catalog-toolbar-compact' into 'main'
fix(admin-ui): compact catalog toolbar (single row)

See merge request 2-6/2-6-4/terravault/ordinis!19
2026-05-10 18:30:49 +00:00
Александр Зимин 5afc233a4b fix(admin-ui): compact catalog toolbar (single row) 2026-05-10 18:30:49 +00:00
Александр Зимин e0292aea45 Merge branch 'feat/catalog-fk-chips' into 'main'
feat(admin-ui): catalog FK chips на cards (← N использ.)

See merge request 2-6/2-6-4/terravault/ordinis!18
2026-05-10 18:26:30 +00:00
Александр Зимин 49ab6c9340 feat(admin-ui): catalog FK chips на cards (← N использ.) 2026-05-10 18:26:30 +00:00
Александр Зимин 2ec45acc6f Merge branch 'feat/catalog-list-with-fk' into 'main'
feat(admin-ui): catalog list view per design handoff (D-list-view)

See merge request 2-6/2-6-4/terravault/ordinis!17
2026-05-10 18:05:21 +00:00
Александр Зимин 75e844eb98 feat(admin-ui): catalog list view per design handoff (D-list-view) 2026-05-10 18:05:21 +00:00
Александр Зимин 71b20c6a09 Merge branch 'docs/design-handoff-catalog-v2-richer' into 'main'
docs(admin-ui): rich design handoff Dictionary Catalog (3 views + nstart/ui patch)

See merge request 2-6/2-6-4/terravault/ordinis!16
2026-05-10 17:59:01 +00:00
Александр Зимин ddbe7368a2 docs(admin-ui): rich design handoff Dictionary Catalog (3 views + nstart/ui patch) 2026-05-10 17:59:01 +00:00
Александр Зимин c60a6b82c5 Merge branch 'feat/catalog-v2' into 'main'
feat(admin-ui): Dictionary Catalog v2 (URL filters + sort + density)

See merge request 2-6/2-6-4/terravault/ordinis!15
2026-05-10 17:37:47 +00:00
Александр Зимин 7e460ebff7 feat(admin-ui): Dictionary Catalog v2 (URL filters + sort + density) 2026-05-10 17:37:47 +00:00
Александр Зимин 7ad358a6f1 Merge branch 'fix/version-banner-frontend-commit' into 'main'
fix(admin-ui): pass git commit к vite через Docker ARG

See merge request 2-6/2-6-4/terravault/ordinis!13
2026-05-10 17:33:44 +00:00
Александр Зимин 215ba678ac fix(admin-ui): pass git commit к vite через Docker ARG 2026-05-10 17:33:44 +00:00
Александр Зимин 69a2477bff Merge branch 'fix/dict-detail-toolbar-wrap' into 'main'
fix(admin-ui): dict detail toolbar — wrap buttons вместо ломки текста

See merge request 2-6/2-6-4/terravault/ordinis!14
2026-05-10 17:30:38 +00:00
Александр Зимин f5fb2b70a6 fix(admin-ui): dict detail toolbar — wrap buttons вместо ломки текста 2026-05-10 17:30:38 +00:00
Александр Зимин 5ecfdb3c08 Merge branch 'fix/build-info-null-git-properties' into 'main'
fix(build): pass git info via Docker build args (unblock main pipeline)

See merge request 2-6/2-6-4/terravault/ordinis!12
2026-05-10 17:10:45 +00:00
Александр Зимин d740092d0c fix(build): pass git info via Docker build args (unblock main pipeline) 2026-05-10 17:10:45 +00:00
Александр Зимин 6a6441d169 Merge branch 'feat/version-banner' into 'main'
feat: version banner UI + GET /api/v1/version backend endpoint

See merge request 2-6/2-6-4/terravault/ordinis!11
2026-05-10 16:50:50 +00:00
Александр Зимин e8f98230f2 feat: version banner UI + GET /api/v1/version backend endpoint 2026-05-10 16:50:50 +00:00
Александр Зимин 17386ba0b4 Merge branch 'docs/design-handoff-dictionary-catalog' into 'main'
docs(admin-ui): design handoff для Dictionary Catalog v2

See merge request 2-6/2-6-4/terravault/ordinis!10
2026-05-10 16:45:26 +00:00
Александр Зимин 94df199529 docs(admin-ui): design handoff для Dictionary Catalog v2 2026-05-10 16:45:26 +00:00
Александр Зимин 11ec6bd43a Merge branch 'ci/dockerfile-future-proof-modules' into 'main'
ci(docker): future-proof backend Dockerfiles (fix root cause from Phase A break)

See merge request 2-6/2-6-4/terravault/ordinis!9
2026-05-10 16:23:04 +00:00
Александр Зимин 51d6709fb3 ci(docker): future-proof backend Dockerfiles (fix root cause from Phase A break) 2026-05-10 16:23:03 +00:00
Александр Зимин 58efa5cfbd Merge branch 'fix/admin-ui-critical-review-issues' into 'main'
fix(admin-ui): top-5 critical review bugs (auth + AJV + TZ + idempotency + storage)

See merge request 2-6/2-6-4/terravault/ordinis!7
2026-05-10 16:22:35 +00:00
Александр Зимин fc2fb35717 fix(admin-ui): top-5 critical review bugs (auth + AJV + TZ + idempotency + storage) 2026-05-10 16:22:35 +00:00
Александр Зимин f2614244bf Merge branch 'fix/dockerfile-include-ordinis-notifications' into 'main'
fix(docker): include ordinis-notifications in 3 Dockerfiles (unblock main pipeline)

See merge request 2-6/2-6-4/terravault/ordinis!8
2026-05-10 15:57:52 +00:00
Александр Зимин 3d5304b308 fix(docker): include ordinis-notifications in 3 Dockerfiles (unblock main pipeline) 2026-05-10 15:57:52 +00:00
Александр Зимин 0275e78907 Merge branch 'feat/notifications-phase-a-foundation' into 'main'
feat(notifications): Phase A foundation — module + schema 0021

See merge request 2-6/2-6-4/terravault/ordinis!5
2026-05-10 15:41:34 +00:00
Александр Зимин 3b7b1bffe5 feat(notifications): Phase A foundation — module + schema 0021 2026-05-10 15:41:34 +00:00
Александр Зимин 419e9b9b01 Merge branch 'ci/serialize-docker-builds-runner-disk' into 'main'
ci(docker): serialize docker builds + prune cache

See merge request 2-6/2-6-4/terravault/ordinis!6
2026-05-10 15:39:26 +00:00
Александр Зимин 38a5c19452 ci(docker): serialize docker builds + prune cache 2026-05-10 15:39:26 +00:00
Александр Зимин c785d1e186 Merge branch 'chore/branding-strip-csod-from-logo' into 'main'
chore(admin-ui): strip "НСИ ЦУОД" from logo

See merge request 2-6/2-6-4/terravault/ordinis!4
2026-05-10 15:20:56 +00:00
Александр Зимин 02167e29f6 chore(admin-ui): strip "НСИ ЦУОД" from logo 2026-05-10 15:20:56 +00:00
Александр Зимин c675c5e310 Merge branch 'fix/nginx-drafts-routing-staging' into 'main'
fix(admin-ui): route /api/v1/drafts/* to writer

See merge request 2-6/2-6-4/terravault/ordinis!3
2026-05-10 14:48:43 +00:00
Александр Зимин 3547b86a3e fix(admin-ui): route /api/v1/drafts/* to writer 2026-05-10 14:48:43 +00:00
Александр Зимин 7c1a469123 Merge branch 'ci/no-cache-blob-recovery' into 'main'
ci(docker): add --no-cache to bypass stale inline cache from missing blob

See merge request 2-6/2-6-4/terravault/ordinis!2
2026-05-10 12:33:57 +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
Александр Зимин 62152cdeb6 Merge branch 'ci/fix-nexus-blob-cache' into 'main'
ci(docker): force --pull on base images to recover from Nexus blob loss

See merge request 2-6/2-6-4/terravault/ordinis!1
2026-05-10 12:01:31 +00: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. 07c5ca04c9 docs(release): v1.1.0 release notes draft + tag message
Готовит ground для тага v1.1.0 после окончания Approval Workflow v2 soak'а
на staging (≥7 дней с 2026-05-08 → target ≥2026-05-15).

- docs-internal/releases/v1.1.0.md — полные release notes:
  Track A (Approval Workflow v2 P0–P4, 8 commits) + Track B (5 CEO v1
  gap closures), migrations 0017-0020, API surface additions, decisions
  D1-D4 rationale, 6 lessons learned, cluster ops checklist, out-of-scope
  list (deferred к v3 design doc).
- docs-internal/releases/v1.1.0-tag-message.txt — готовый текст для
  `git tag -a v1.1.0 -F docs-internal/releases/v1.1.0-tag-message.txt`.

NOT тагаем сейчас — soak ahead. Documentация заранее = меньше work
после soak conclusion. Если в soak найдутся issues, release notes
проще обновить чем писать с нуля.
2026-05-10 12:31:57 +03:00
Zimin A.N. 361f3b431c feat(approval): bulk approve/reject в /reviews queue (Phase 4 polish)
Phase 4 last optional UX из design doc — bulk operations:

- Multi-select column в reviewer queue (Checkbox per row + select-all
  header). Set<draftId> selection state, persisted между refetch'ами.
- "Approve выбранные" — confirm dialog → parallel approveDraft mutations
  через Promise.all. Каждая mutation independent: если одна fail
  (409 self_approve_forbidden / draft_not_pending race), остальные
  продолжают.
- "Reject выбранные" — Modal с TextArea для shared reason (required,
  matches backend reject_reason_required validation). Reason одинаковый
  для всех — bulk обычно "duplicate registration" / "wrong scope" / etc.
- Bulk result Alert: approved count / rejected count / failed list с
  per-draft reason'ами (err code из 409). User видит точно что прошло
  и что нет.
- Auto-clear selection после bulk operation. Queue refetch'ит через
  refetchInterval=30s, плюс mutation onSuccess invalidate'ит ['drafts'].

i18n RU/EN: 22 keys (selectAll/selectRow/selectedCount/approveSelected/
rejectSelected/clear/confirmApprove/rejectModal*/confirmReject/cancel/
resultTitle/approvedCount/rejectedCount/failedCount). Pluralization
через i18next plurals.

Why ship without soak data:
- Implementation не зависит от queue depth — это просто loop'ит над
  выбранными drafts.
- Reviewer на любом dict с queue >5 уже выгадывает время.
- Если queue staying small после soak — feature не bothering нас (UI
  toolbar показывается только когда anySelected).

Tests: vitest 89/89, tsc clean, prod build clean.
2026-05-10 12:12:57 +03:00
Zimin A.N. aedeafc84e feat(approval): /my-drafts maker self-tracking view + /api/v1/drafts/me
Approval Workflow v2 Phase 4 — UX:

Backend:
- DraftController.myDrafts: GET /api/v1/drafts/me?page&size — все статусы
  (PENDING/APPROVED/REJECTED/WITHDRAWN) текущего authenticated user, sorted
  submitted_at DESC. Resolve maker_id из JWT subject (fallback "anonymous"
  если auth off).
- Reuses existing DraftService.listByMaker (Phase 1 method, был не exposed).

Frontend:
- routes/my-drafts.tsx: новый маршрут с табличным view.
  Колонки: businessKey / operation / status / submittedAt / reviewedAt /
  reviewerId / comment / actions. Status badge tinted (warning/success/
  error/neutral). PENDING строки имеют Withdraw кнопку (calls existing
  useWithdrawDraft с confirm).
- api/queries.ts: useMyDrafts (refetch 30s — maker увидит когда reviewer
  decide).
- api/mutations.ts: useWithdrawDraft теперь invalidate'ит ['drafts'] —
  /me + by-dict обе видят изменение.
- routes/__root.tsx: nav link "Мои черновики" / "My drafts" (после Reviews).
- i18n RU/EN: 18 keys (nav.myDrafts, myDrafts.* col/action/status).
  Pluralization для total через i18next plurals.

Why это нужно:
- До этого maker submit'нул draft и пропадал — единственный feedback был
  badge "На review" в records list (только для текущего dict). Здесь — все
  его submissions с history + withdraw для PENDING.
- Reviewer pool (D3=A) маленький, /reviews queue только для них. Maker
  без role'а не видит queue, но должен tracking своих submissions.

Tests: ordinis-rest-api unit 119/119, ordinis-app e2e 28/28, vitest 89/89.
2026-05-10 12:07:35 +03:00
Zimin A.N. b2e7fd84c9 feat(approval): Approval Workflow v2 Phase 4 monitoring + pending-review badges
Approval Workflow v2 — Phase 4: monitoring + UX visibility.

Backend metrics (Micrometer → Prometheus):
- Gauge nsi_draft_pending_total — global queue depth (polled на scrape).
- Counter nsi_draft_submit_total{dictionary,operation,outcome=ok|already_pending|error}
  — submit attempts с разбивкой по outcome. Phase 3 lessons: error outcome
  выявит schema bugs которые ранее маскировались.
- Counter nsi_draft_decision_total{dictionary,operation,outcome=approved|rejected|withdrawn}
  — counts decisions, разбит по типу.
- Timer nsi_draft_review_duration_seconds — time-to-decide histogram (от
  submit до approve/reject/withdraw). publishPercentileHistogram() — для
  Prometheus histogram_quantile p95/p99.

DraftService:
- Optional<MeterRegistry> — null-safe в test ctor'ах.
- @PostConstruct registerGauges() — Gauge.builder polled от
  RecordDraftRepository.countPending().
- recordDecision() helper в approve/reject/withdraw — записывает Timer +
  Counter одной точкой.
- 4-arg ctor compat для Phase 1 unit tests.

UI (admin):
- useDictPendingDrafts — per-dict pending list, refetch 30s, gated на
  detailQuery.data.approvalRequired (no-op для не-approval dicts).
- "На review" badge в businessKey cell records table — моментальная
  видимость что есть pending draft. Open /reviews для act'а.
- i18n RU/EN: dict.pendingReview.label + tooltip.

Domain:
- RecordDraftRepository.countPending() — для Gauge polled count.

Tests: ordinis-rest-api unit 119/119, ordinis-app e2e 28/28, vitest 89/89.
2026-05-08 15:11:25 +03:00
Zimin A.N. d229c545f9 feat(approval): bundle-driven approval policy + spacecraft/ground_station soak flag
Approval Workflow v2 Phase 3 — soak prep:

- BundleManifest.DictionaryEntry: добавлены опциональные approvalRequired
  (Boolean) и approvalMinRole (String).
- CuodBundleImporter: применяет policy на fresh + on-update + при стабильной
  схеме (flip без bumping schema_version). Apply ТОЛЬКО когда явно изменился
  → не штормим UPDATE'ами.
- CuodBundleProperties.honorApprovalPolicy (default false): env-gated guard.
  Bundle ship'ит approvalRequired:true для пилотных dicts, prod игнорирует
  пока не явно включит.
- manifest cuod 1.3.0 → 1.3.1: spacecraft + ground_station получили
  approvalRequired:true. Без honor flag — no-op.

Этого commit'а достаточно для соответствующего helm change в
ordinis-infra (bundleHonorApprovalPolicy=true в values-staging) — flip'ает
spacecraft/ground_station на staging при следующем deploy. Prod остаётся
default=false до окончания soak.
2026-05-08 14:59:34 +03:00
Zimin A.N. 9ef9b8147e feat(approval): Approval Workflow v2 Phase 3 — e2e suite + 0020 case fix
Phase 3 e2e coverage (8 tests, ApprovalWorkflowE2ETest):
- happyFlow: maker submit → checker approve → live record + outbox event
- rejectFlow: keeps draft REJECTED, no live record, re-approve blocked
- selfApprove: same JWT subject blocked (self_approve_forbidden / self_reject_forbidden)
- twoPendingDrafts: exclusion constraint → 409 draft_already_pending
- directCRUD on approval_required dict: POST/PUT/DELETE → 409 draft_required (D2=A)
- regression: dict без approval_required — direct CRUD по-прежнему работает
- withdrawByMaker: maker может, reviewer не может (403 not_draft_maker)
- rejectWithoutComment: 400 reject_reason_required

E2e exposed real Phase 0 bug:
Hibernate @Enumerated(EnumType.STRING) пишет UPPERCASE ('PENDING'), а
Liquibase 0019 check constraint + exclusion WHERE использовали lowercase
('pending'). Каждый insert проваливался на check, маппился в
DataIntegrityViolation, и DraftService.submit маскировал его в
draft_already_pending — скрывая реальную причину.

Fixes:
1. Migration 0020: пересоздание record_drafts_status_check + exclusion
   с UPPERCASE значениями (matches enum serialization).
2. DraftService.submit: только SQLSTATE 23P01 (exclusion violation)
   маппится в draft_already_pending. Остальные DataIntegrity (check, FK,
   NOT NULL) пробрасываются как-есть — иначе schema bugs продолжат
   маскироваться.
3. DraftServiceTest StubDraftRepo: оборачивает SQLException 23P01 в
   DataIntegrityViolation cause chain — отражает прод поведение.

Test counts:
- ordinis-rest-api unit: 119/119 PASS
- ordinis-app e2e: 28/28 PASS (был 20, +8 новых ApprovalWorkflowE2ETest)
2026-05-08 13:52:17 +03:00
Zimin A.N. 84a4de4ceb feat(approval): Approval Workflow v2 Phase 2 — admin UI reviewer queue + toggle
Phase 2 admin UI на скелете Phase 0/1 (e438c4c, 2020af9). Делает workflow
end-to-end usable: reviewer'ы могут approve/reject через UI; admins
включают approval_required per-dict через checkbox.

API surface (admin-ui):
- types: DraftStatus, DraftOperation, DraftResponse, SubmitDraftRequest,
  ReviewQueuePage. + DictionaryDefinition: approvalRequired, approvalMinRole.
- queries (react-query):
  * useReviewQueue (page, size) — global queue, refetch 30s.
  * useDraft(id) — single draft for diff drawer.
  * useLiveRecord(dict, bk) — current live record for side-by-side comparison.
- mutations:
  * useSubmitDraft(dict) — POST /dictionaries/{n}/records/drafts
  * useApproveDraft() — POST /drafts/{id}/approve
  * useRejectDraft() — POST /drafts/{id}/reject (reason required)
  * useWithdrawDraft() — DELETE /drafts/{id}

New /reviews route:
- Table queue: dict, business_key, operation badge, maker, submitted_at.
- Click "Просмотр" → side-by-side drawer:
  * left: current live (если есть; для CREATE — placeholder).
  * right: proposed (или CLOSE notice).
  * maker comment chip если есть.
  * Reviewer comment input + Approve / Reject buttons.
  * Reject disabled пока comment пустой (mirror's backend 400).
- Auto-invalidates на success — queue refresh, list updates.
- Nav link "Согласования" в header.

DictionaryEditorDialog Metadata tab:
- New checkbox "Требовать approval (maker-checker)" — orbit/8 styled
  (отделимо от Redis projection card visually).
- При checked → shown approval_min_role TextInput (e.g. "ordinis:internal").
- payload sends approvalRequired + approvalMinRole.

i18n RU/EN:
- nav.reviews, reviews.{title,description,empty,queueTotal_*,col.*,
  action.*, drawer.*}
- schema.approvalRequired.{label,hint}, schema.approvalMinRole.{label,hint}
- Plurals для RU queueTotal (one/few/many/other).

Verify:
- pnpm tsc --noEmit: clean.
- pnpm test (vitest): 89/89 PASS.
- pnpm build: clean.
- routeTree.gen.ts auto-regenerated с /reviews.

Phase 3 (next session):
- e2e tests: full flow maker submit → checker approve → record live + outbox event.
- e2e: reject keeps draft, doesn't publish.
- e2e: self-approve blocked (no_self_approve constraint).
- e2e: two pending drafts на one business_key blocked (one_pending_per_key).
- Per-dict opt-in soak: 1-2 dicts (например ground_station) с
  approval_required=true в staging.
2026-05-08 13:28:56 +03:00