Commit Graph

561 Commits

Author SHA1 Message Date
Александр Зимин 4bdc7d2245 Merge branch 'feat/sidebar-role-based-visibility' into 'main'
feat(sidebar): role-based visibility для /reviews

See merge request 2-6/2-6-4/terravault/ordinis!166
2026-05-13 10:54:24 +00:00
Zimin A.N. 5fb1658880 feat(sidebar): role-based visibility для /reviews
Phase 1d enhancement: 'Согласования' пункт сайдбара скрыт если у user'а
нет approver/reviewer ролей. Раньше показывался любому authenticated —
оператор без reviewer прав видел queue, кликал и получал пустую
страницу (или 403 если бы пробовал approve).

Visibility rules:
- 'Мои черновики' — auth (maker может быть любой)
- 'Согласования' — auth + (ordinis:schema:reviewer OR ordinis:record:reviewer
  OR ordinis:admin super-role) — иначе hidden
- 'Outbox' / 'Webhooks' / 'Аудит' — auth (admin-y но backend не gated
  отдельной ролью)

Новый NavItem.reviewerOnly flag — фильтр в visibleSections учитывает
оба flag'а (authRequired + reviewerOnly).

Применено и к desktop Sidebar и к MobileSidebar (DRY refactor — отдельный
task).
2026-05-13 13:52:40 +03:00
Александр Зимин efe3fe08e5 Merge branch 'fix/workflow-roles-autowire-ctor' into 'main'
fix(auth): @Autowired на WorkflowRoles main ctor (super-role override fix)

See merge request 2-6/2-6-4/terravault/ordinis!165
2026-05-13 10:51:38 +00:00
Zimin A.N. 4fe7f1e800 fix(auth): @Autowired на WorkflowRoles main ctor
Класс имеет два constructor'а (main + test). Без явной @Autowired
аннотации Spring может выбрать no-arg ctor → props=null →
rolesClaimPath() fallback на realm_access.roles несмотря на
env var ORDINIS_AUTH_ROLES_CLAIM=resource_access.ordinis.roles.

Симптом: 403 forbidden_role с message 'Требуется realm role:
ordinis:schema:reviewer' для user с ordinis:admin в
resource_access.ordinis.roles (super-role override не работал
потому что currentRoles() возвращал пустой set из неправильного
claim path).

Fix: @Autowired на main ctor — Spring выбирает его, props
инжектится, rolesClaim читается из env var правильно.
2026-05-13 13:49:07 +03:00
Александр Зимин 19f560e60a Merge branch 'chore/restore-nstart-runner-tag' into 'main'
chore(ci): restore nstart tag (revert !157)

See merge request 2-6/2-6-4/terravault/ordinis!164
2026-05-13 10:33:21 +00:00
semantic-release-bot e183ff1d6c chore(release): v2.18.0
## [2.18.0](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/compare/v2.17.0...v2.18.0) (2026-05-13)

###  Features

* **auth:** WorkflowRoles configurable claim path + ordinis:admin super-role ([36c06ae](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/36c06ae1da52ebf515eaa5227c942f8343071188))
2026-05-13 10:29:04 +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
Александр Зимин d0f49250eb Merge branch 'feat/phase-1d-configurable-claim-path-admin-override' into 'main'
feat(auth): configurable claim path + ordinis:admin super-role

See merge request 2-6/2-6-4/terravault/ordinis!163
2026-05-13 10:20:59 +00:00
Zimin A.N. 36c06ae1da feat(auth): WorkflowRoles configurable claim path + ordinis:admin super-role
User report: token has roles в resource_access.ordinis.roles (client-scoped),
backend WorkflowRoles читал hardcoded realm_access.roles → forbidden_role
для всех reviewer actions.

Backend:
- WorkflowRoles: depends OrdinisAuthProperties → reads via rolesClaim path
  (же что ScopeContext). Default realm_access.roles, на staging/prod env
  var ORDINIS_AUTH_ROLES_CLAIM=resource_access.ordinis.roles (уже wired
  в helm helper).
- Nested claim path resolver — mirror ScopeContext.readClaimByPath
- New super-role ADMIN ('ordinis:admin') — holder автоматически проходит
  любой workflow check без явного назначения отдельных ролей. Convenience
  для small teams.

Frontend (usePermissions.ts):
- tokenRoles() читает оба claim path (realm_access.roles +
  resource_access.ordinis.roles) и объединяет — UI gate работает
  независимо от backend config
- hasRole() recognizes 'ordinis:admin' как super-role override
- ROLE_ADMIN exported

Docs:
- docs/operator/rbac.md: добавлен ordinis:admin row, JWT claim path note,
  Admin profile updated (super-role vs composite explanation)
2026-05-13 13:17:54 +03:00
semantic-release-bot 031a582167 chore(release): v2.17.0
## [2.17.0](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/compare/v2.16.4...v2.17.0) (2026-05-13)

###  Features

* **auth:** Phase 1d — backend role enforcement для workflow ([24aa94d](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/24aa94d13007b7d2d9716a6afb7fdbc54a32d8ba))

### ♻️ Refactoring

* **auth:** domain-specific role names (consistent convention) ([c5b00ff](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/c5b00ff62314942f6b2cee09993d9ddd8648e8e2))

### 📝 Docs

* **rbac:** добавить матрицу realm-ролей Keycloak ([1dba947](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/1dba9476db036d4f5688d409aae4c39b5b5a441c))
2026-05-13 10:06:16 +00:00
Александр Зимин 47b47f4abc Merge branch 'feat/phase-1d-workflow-roles' into 'main'
feat(auth): Phase 1d — backend role enforcement для workflow

See merge request 2-6/2-6-4/terravault/ordinis!162
2026-05-13 09:57:44 +00:00
Zimin A.N. ce3a29f08d test(e2e): assign ordinis:record:reviewer role в reviewer JWT tokens
Phase 1d enforcement в DraftService.approve()/.reject() требует
realm role 'ordinis:record:reviewer'. Test fixtures используют
JwtTestSupport.signJwt() — добавляем role в JWT для reviewer'ов.

Changes в ApprovalWorkflowE2ETest:
- Новый helper reviewerTokenFor(subject) — internal scope + record:reviewer
- 4 reviewer call sites (bob/dave/julia/leo) → reviewerTokenFor()
- eve-solo (selfApproveBlocked test) тоже → reviewerTokenFor() чтобы
  достичь self-approve check, иначе forbidden_role срабатывает раньше

Maker callsites (alice/carol/frank/ivan/kate) остаются на tokenFor()
без reviewer role — backend гейтит только approve/reject методы.

Fix для 4 failures в pipeline 6898:
- happyFlow_submitApprove_recordLiveAndOutboxEvent
- rejectFlow_keepsDraft_noLiveRecord
- selfApproveBlocked_409
- rejectWithoutComment_400
2026-05-13 12:55:15 +03:00
Zimin A.N. 1dba9476db docs(rbac): добавить матрицу realm-ролей Keycloak
operator/rbac.md — новая страница с полной матрицей RBAC:
- Scope ACL (ordinis:client:public/internal/restricted)
- Workflow роли (schema:reviewer, schema:publisher, record:reviewer)
- Action matrix (endpoint → required scope + role)
- Типичные профили (consumer, maker, reviewer, publisher, admin)
- Composite role 'ordinis:admin' для one-shot assignment
- Keycloak setup инструкция (Admin Console шаги)
- Диагностика типичных 403 ошибок

toc.yaml — link в раздел Руководство оператора.
operator/index.md, reviewer/index.md — cross-references к rbac.md.

Покрывает Phase 1d enforcement (MR !162) — пользователи теперь
знают какие роли назначать в Keycloak realm 'nstart'.
2026-05-13 12:47:50 +03:00
Zimin A.N. c5b00ff623 refactor(auth): domain-specific role names (consistent convention)
User feedback: универсальный 'ordinis:approver' отступал от convention
'ordinis:<domain>:<action>'. Меняю на domain-specific:

WorkflowRoles constants:
- APPROVER → SCHEMA_REVIEWER ('ordinis:schema:reviewer')
- PUBLISHER → SCHEMA_PUBLISHER ('ordinis:schema:publisher')
- Новая RECORD_REVIEWER ('ordinis:record:reviewer')

Applied:
- SchemaDraftService.decide() → require SCHEMA_REVIEWER
- SchemaDraftService.publish() → require SCHEMA_PUBLISHER
- DraftService.approve() / .reject() → require RECORD_REVIEWER

Frontend usePermissions.ts:
- ROLE_APPROVER + ROLE_PUBLISHER → ROLE_SCHEMA_REVIEWER +
  ROLE_SCHEMA_PUBLISHER + ROLE_RECORD_REVIEWER
- Новый hook useCanReviewRecord() для record draft actions

Keycloak setup (updated):
- ordinis:schema:reviewer
- ordinis:schema:publisher
- ordinis:record:reviewer
(Composite role 'ordinis:admin' = all три + scope roles — для удобства.)
2026-05-13 12:44:23 +03:00
Zimin A.N. 24aa94d130 feat(auth): Phase 1d — backend role enforcement для workflow
User explicitly requested 'Phase 1d пора'. До этого MR'а backend гейтил
только maker-checker (self_approve_forbidden), frontend hooks возвращали
auth.isAuthenticated.

Реализация:

Backend WorkflowRoles (ordinis-rest-api/auth/WorkflowRoles.java):
- Component reads realm_access.roles из JWT claim
- Constants: ordinis:approver (decide/approve/reject), ordinis:publisher (publish)
- requireRole(role) throws 403 forbidden_role если missing
- Defensive parse: malformed/missing realm_access → empty roles
- 9 unit tests cover authenticated + anonymous + malformed JWT

Applied gates:
- SchemaDraftService.decide() — require ordinis:approver
- SchemaDraftService.publish() — require ordinis:publisher
- DraftService.approve() — require ordinis:approver
- DraftService.reject() — require ordinis:approver

WorkflowRoles is Optional в test ctors → unit tests без auth setup
не ломаются. Все existing tests должны проходить (legacy ctor overloads
сохранены).

Frontend usePermissions.ts:
- Stub return auth.isAuthenticated заменён на real role decode из
  auth.user.profile.realm_access.roles
- ROLE_APPROVER + ROLE_PUBLISHER constants exported
- Defensive parsing — graceful с missing/malformed claims
- useCanCreateSchemaDraft остаётся permissive (любой auth — maker'ом
  может быть anyone, scope ACL заведует видимостью)

Naming convention — colon-separated (consistent с ordinis:client:*
scope ролями), aligned с MR !161.

MIGRATION (BREAKING без правильной Keycloak setup):
1. Keycloak realm 'nstart' → Realm Roles → Create:
   - ordinis:approver
   - ordinis:publisher
2. Users → assign roles:
   - reviewer/approver users → ordinis:approver
   - publisher users → ordinis:approver + ordinis:publisher
3. После refresh JWT (logout/login) роли появляются в realm_access.roles

Без этого все decide/publish операции вернут 403 forbidden_role.
2026-05-13 12:36:53 +03:00
Александр Зимин 8bc6916dbf Merge branch 'chore/rename-schema-roles-colon-convention' into 'main'
chore(auth): rename schema workflow roles к colon-convention

See merge request 2-6/2-6-4/terravault/ordinis!161
2026-05-13 09:29:51 +00:00
Zimin A.N. 1281654d32 chore(auth): rename schema workflow roles к colon-convention
Project uses Altum-style colon-separated realm roles:
- ordinis:client:public / internal / restricted (scope ACL, уже active)

Schema workflow roles (Phase 1d TODO) приводим к той же convention:
- ordinis:schema:reviewer (был ordinis-schema-reviewer)
- ordinis:schema:publisher
- ordinis:schema:maker

Изменения в usePermissions.ts:
- JSDoc обновлён с новой convention
- Constants ROLE_SCHEMA_* для discoverable rename'а в Phase 1d
- TODO comments указывают на constants, не bare strings

Phase 1d (когда подключится backend role check):
1. Uncomment role check в hooks
2. Backend SchemaDraftService.decide() — require role
2026-05-13 12:27:33 +03:00
semantic-release-bot dc556d12c8 chore(release): v2.16.4
## [2.16.4](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/compare/v2.16.3...v2.16.4) (2026-05-13)

### 🐛 Fixes

* **ui:** shared UserCell для display UUID actor'ов везде ([e6294ce](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/e6294ce79cc07c6f57c8d45e81a92507e01bb97b))
* **ui:** кнопки в schema draft drawer — RU label + flow row ([6ee88a8](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/6ee88a86bd32cfaa34ceb8dee140c00e629172b8))
2026-05-13 08:53:35 +00:00
Александр Зимин 97c2835bcf Merge branch 'fix/user-display-helper-reviews-audit' into 'main'
fix(ui): shared UserCell для UUID actor display (reviews + audit + drawer)

See merge request 2-6/2-6-4/terravault/ordinis!160
2026-05-13 08:48:00 +00:00
Zimin A.N. 6ee88a86bd fix(ui): кнопки в schema draft drawer — RU label + flow row
User feedback (screenshot): 'Approve' остался английским в RU локали +
'Отозвать' улетал на отдельную строку справа из-за ml-auto.

Fix:
- i18n.ts: 'workflow.schemaDraft.actions.approve' default 'Approve' →
  'Одобрить'. EN остался 'Approve'.
- SchemaDraftDrawer.tsx: убрал ml-auto на withdraw button. Раньше это
  force pushed его в конец строки → flex-wrap отправлял на новую строку
  правым краем. Сейчас withdraw сидит сразу после reject в той же
  flow-row, с danger color для визуальной distinction. На узком экране
  всё ещё wrap'нется естественно (без 'плавающего' alignment).
2026-05-13 11:45:29 +03:00
Zimin A.N. e6294ce79c fix(ui): shared UserCell для display UUID actor'ов везде
Browser QA на staging показал что UUID actor'а render'ится raw во многих
местах кроме drawer (MR !158 — там был inline fix):
- /reviews Records tab — column AUTHOR
- /reviews Schemas tab — column AUTHOR
- /audit log — column USER

77ec2cc8-98e3-4d70-8037-94038fcbde67 — нечитаемо для оператора. Тот же
паттерн что я делал в MR !158, теперь shared helper:

src/lib/useUserDisplay.tsx:
- useUserDisplay(uuid) hook — returns { display, isMe, fullId }
- UserCell component — readonly cell с title=fullId tooltip
- Match current user (JWT sub claim) → 'Я • <preferred_username>'
- Other user → short UUID (8 chars) + full в title
- null/undefined → 'anonymous'

Применено:
- reviews.tsx:334,625 — records + schemas table AUTHOR cells
- audit.tsx:476 — USER cell
- SchemaDraftDrawer.tsx — refactor (был inline в MR !158 → теперь shared)

TODO Phase 1d: backend endpoint /admin/users/{sub}/display чтобы и для
не-current-user отображать username.
2026-05-13 11:43:04 +03:00
semantic-release-bot 38764648bf chore(release): v2.16.3
## [2.16.3](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/compare/v2.16.2...v2.16.3) (2026-05-13)

### 🐛 Fixes

* **sidebar,audit:** badges для Outbox + Webhooks, clarify audit scope label ([aec2cdd](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/aec2cdd644b8d6bf1846f0d26d1485ef669f5f98))
* **types:** SchemaReviewQueuePage.total → totalElements (match backend) ([aecd65e](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/aecd65e7d57a6853bf0744571da246d6db236071))
2026-05-13 08:10:21 +00:00
Александр Зимин 6e5691028b Merge branch 'fix/schema-review-queue-page-total-field' into 'main'
fix(sidebar,audit): badges fix + outbox/webhooks counts + clarify audit scope label

See merge request 2-6/2-6-4/terravault/ordinis!159
2026-05-13 08:05:15 +00:00
Zimin A.N. aec2cdd644 fix(sidebar,audit): badges для Outbox + Webhooks, clarify audit scope label
Sidebar:
- Outbox: бэйдж = outbox.pending (события queued, не отосланы). DLQ
  отдельно — alarming но не actionable из бэйджа, видно на /outbox.
- Webhooks: бэйдж = count активных subscriptions (w.active === true).
  User feedback: 'вебхуки активные есть а цифры с бэйджем нет'.

Audit log column header:
- 'Scope' → 'Доступ актора' (ru) / 'Actor scope' (en)
- Backend audit_log хранит userScope (claim из JWT актора), НЕ scope
  изменённого ресурса. User confusion: 'правки в public были а тут
  RESTRICTED' — потому что у пользователя scope-привилегия RESTRICTED.
  Уточнённая label убирает путаницу.
- TODO: backend feature — добавить dictionaryScope в AuditEntry чтобы
  показать обе ('Актор: RESTRICTED → Ресурс: PUBLIC' — двусторонний
  audit trail для compliance).
2026-05-13 11:03:47 +03:00
Zimin A.N. aecd65e7d5 fix(types): SchemaReviewQueuePage.total → totalElements (match backend)
User reported бэйдж 'Согласований' в sidebar показывал 0 несмотря на 1
pending schema draft. Root cause:

Backend сериализует Spring Page<SchemaDraft> как:
{items, page, size, totalElements, totalPages}

Но TS type SchemaReviewQueuePage объявлял поле как 'total' (mismatch
именования). Consumers читали data?.total → undefined → fallback 0:

- useReviewsBadgeCount (queries.ts:650) — sidebar badge падал на 0
- reviews.tsx:61 — header count в Schemas tab (но fallback на
  items.length скрывал bug в UI)
- reviews.tsx:593-594 — schema queue subtitle 'N drafts ждут ревью'

Fix:
- client.ts SchemaReviewQueuePage: total → totalElements + totalPages
- 3 consumer'а обновлены на .totalElements
- Backend контракт не меняется — это чисто frontend type alignment

После prod deploy v2.16.3 sidebar badge заработает корректно.
2026-05-13 10:58:48 +03:00
semantic-release-bot 894fcea81b chore(release): v2.16.2
## [2.16.2](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/compare/v2.16.1...v2.16.2) (2026-05-13)

### 🐛 Fixes

* **schema-draft:** автор display + gate Отозвать только maker'у ([fbf978a](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/fbf978aa397b66a309fafb35caa611e3f73164b8))
* **sidebar:** подключить reviews badge к real count ([252939f](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/252939f63a9c997b94603aefa43b9bae287f3eb3))
2026-05-13 07:46:14 +00:00
Александр Зимин 441bbedadf Merge branch 'fix/schema-draft-author-display-and-withdraw-gate' into 'main'
fix(schema-draft): author display + gate Отозвать только maker'у

See merge request 2-6/2-6-4/terravault/ordinis!158
2026-05-13 07:39:59 +00:00
Александр Зимин 137c07c9e4 Merge branch 'fix/sidebar-reviews-badge-count' into 'main'
fix(sidebar): подключить reviews badge к real count

See merge request 2-6/2-6-4/terravault/ordinis!156
2026-05-13 07:35:03 +00:00
Zimin A.N. fbf978aa39 fix(schema-draft): автор display + gate Отозвать только maker'у
Два бага в SchemaDraftDrawer (видны на review pending):

1. АВТОР показывал raw UUID (77ec2cc8-98e3-4d70-8037-...). Резолвим:
   - Если maker = current user (JWT sub claim) → 'Я • <preferred_username>'
   - Иначе → short UUID (первые 8 символов) с full в title attribute
   - Стилизация: 'Я' — text-ink (читаемая), чужой — font-mono text-mute
   - TODO Phase 1d: backend endpoint для resolve чужих sub → display name

2. Кнопка 'Отозвать' показывалась всем юзерам, не только автору.
   - Backend всё равно 403'нет non-maker withdraw — но UI confusion:
     reviewer видел кнопку которая всегда fail'ит.
   - Gate: показываем только когда auth.user.profile.sub === draft.makerId.
   - Reviewer теперь видит чистый action row (Approve / Запросить правки /
     Отклонить), без danger-button мимо контекста.

Реальный случай: см. user screenshot на v2.16.x — Reviewer modal с UUID
автора и кнопкой 'Отозвать' справа внизу.
2026-05-13 10:33:02 +03:00
Александр Зимин 354561f192 Merge branch 'ci/remove-nstart-runner-tag' into 'main'
ci: remove nstart tag из default runner constraint

See merge request 2-6/2-6-4/terravault/ordinis!157
2026-05-13 07:30:48 +00: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. 252939f63a fix(sidebar): подключить reviews badge к real count
Бэйдж 'На ревью' в sidebar (desktop + mobile) был захардкожен в 0 —
TODO comment ждал cheap /drafts/pending/count endpoint. На самом деле
existing /admin/reviews/pending и /admin/schema-reviews/pending уже
возвращают paged response с totalElements/total в metadata — request
size=1 даёт нужный counter с минимальным payload (~1 строка + 4
metadata field'а).

Новый hook useReviewsBadgeCount():
- Параллельно дёргает обе queue (record + schema)
- Суммирует totalElements (record) + total (schema)
- Полл каждые 60s (lighter чем 30s queue refresh — badge не critical)
- Gated на auth.isAuthenticated (anonymous не должен дёргать admin endpoint'ы)

Sidebar.tsx (оба варианта — desktop + mobile MobileSidebar):
- reviewsCount: 0 → reviews.total
2026-05-13 10:08:57 +03:00
Александр Зимин 0d157de803 Merge branch 'ci/forbid-skip-ci-literal' into 'main'
ci(lint): forbid literal skip-ci marker в commit messages

See merge request 2-6/2-6-4/terravault/ordinis!155
2026-05-12 22:00:45 +00: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
semantic-release-bot 1e5e6b59cb chore(release): v2.16.1
## [2.16.1](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/compare/v2.16.0...v2.16.1) (2026-05-12)

### 🐛 Fixes

* **ci:** use printf для commit/MR description avoiding YAML literal block break ([cac6b6a](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/cac6b6a98180faab308594912248a26e01f2f97a))
* **release:** remove [skip ci] из semantic-release commit message ([b7e1f12](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/b7e1f12bc47031f7a1b817a2d025d5472712220e))
2026-05-12 21:39:15 +00:00
Александр Зимин 75474bca45 Merge branch 'ci/auto-pr-prod-image-bump' into 'main'
ci(release): auto-PR в ordinis-infra с image tag после release

See merge request 2-6/2-6-4/terravault/ordinis!154
2026-05-12 21:33:04 +00:00
Zimin A.N. 0c0478914c ci: re-trigger pipeline (previous commit had skip-ci marker literal in description) 2026-05-13 00:26:18 +03:00
Zimin A.N. b7e1f12bc4 fix(release): remove [skip ci] из semantic-release commit message
[skip ci] в commit message skip'ает GitLab pipeline для ВСЕХ ref'ов на
этом SHA — включая tag pipeline. Эффект: semantic-release создаёт v*.*.*
tag, но tag pipeline скипается → images с tag'ом v*-*-*-<sha> не
собираются → propose-prod-image-bump (этот MR) не имеет images для bump.

Confirmed на v2.16.0 (commit bf07d6b): tag pipeline 6814 = skipped,
registry без 'v2-16-0-*' tags → невозможно deploy.

Loop prevention для chore(release) commit'ов работает через releaseRules
('chore': release: false) — semantic-release на этом commit'е detect'ит
'no releasable commits since previous tag' → exit 0 → no new tag → no loop.

Cost: docker-admin-ui ребилдит на каждом chore(release) commit (package.json
+ CHANGELOG.md match frontend-changes path-filter) — ~2 мин wasted CI time,
приемлемо vs broken release flow.
2026-05-13 00:25:48 +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
semantic-release-bot bf07d6b3e3 chore(release): v2.16.0 [skip ci]
## [2.16.0](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/compare/v2.15.0...v2.16.0) (2026-05-12)

###  Features

* **updateBanner:** «Что нового» button открывает modal с changelog ([e46598c](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/e46598c47d01479fae4d6d84a704792ba06a30b4))

### 🐛 Fixes

* **restapi:** NoResource/NoHandler → 404, не 500 ([f2adc37](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/f2adc37e2ef6d22a09759dd6943b0b586d66edce))
2026-05-12 21:18:11 +00:00
Александр Зимин c59724125e Merge branch 'fix/no-resource-found-returns-404' into 'main'
fix(restapi): NoResource/NoHandler exceptions → 404, не 500

See merge request 2-6/2-6-4/terravault/ordinis!153
2026-05-12 21:10:20 +00:00
Zimin A.N. 000322d6ee test(restapi): drop /swagger-ui from 404 test (env-dependent)
Failed pipeline 6805: /swagger-ui/index.html в test profile вернул 200
(springdoc enabled по умолчанию через ORDINIS_SWAGGER_UI_ENABLED default
true). Это валидно — Swagger render'ит реальную UI page.

Замена: проверяем generic non-API typo path который точно не имеет
controller'а и не SPA-route'д. Покрывает тот же NoResourceFoundException
branch handler'а независимо от springdoc env config.
2026-05-13 00:07:17 +03:00
Александр Зимин 3021bd7e3c Merge branch 'feat/whats-new-modal-from-banner' into 'main'
feat(updateBanner): «Что нового» button открывает modal с changelog

See merge request 2-6/2-6-4/terravault/ordinis!152
2026-05-12 21:04:56 +00:00
Zimin A.N. f2adc37e2e fix(restapi): NoResource/NoHandler → 404, не 500
GlobalExceptionHandler catch-all Exception → 500 ловил NoResourceFoundException
и NoHandlerFoundException. Симптомы на prod:

- /swagger-ui/index.html при disabled springdoc возвращал 500 (UI всё равно
  не рендерился, но spring логировал 'Unhandled exception' на каждый запрос —
  спамил alerting и затруднял поиск настоящих ошибок).
- GET /api/v1/typo от integrator'ов давал 500 вместо 404, осложняло
  debugging без traceId-correlation.
- Любой stale frontend bundle с переименованным endpoint мог триггерить
  false-positive incident alerts.

Fix: explicit @ExceptionHandler для обоих exception типов → 404 not_found.
Логирование DEBUG (не WARN/ERROR) — это ожидаемый 404, не аномалия.

Test: SmokeE2ETest.unknownPath_returns404WithNotFoundCode покрывает оба пути
(/api/v1/* и /swagger-ui/*) → 404 + body.code='not_found'.
2026-05-13 00:00:36 +03:00
Zimin A.N. e46598c47d feat(updateBanner): «Что нового» button открывает modal с changelog
Per user feedback: «по кнопке что нового должно открываться модальное
окно с изменениями».

UpdateBanner кнопка «Что нового» (т9n key `updateBanner.docs`) ранее
открывала `/docs/` в новой вкладке — generic documentation page, не
contextual для «что именно в этом обновлении».

Теперь — открывает `WhatsNewModal` (тот же modal что показывает 🎁
button в TopBar) — public changelog с release notes сразу.

## Refactor

- **`WhatsNewModal.tsx` (NEW)** — extracted controlled-mode modal с
  release notes. Props: `open`, `onClose`. Internal logic читает
  changelog, tracks lastSeen в localStorage, на open mark'ает latest
  как seen.
- **`WhatsNewButton.tsx`** — refactored: trigger button + use
  `WhatsNewModal`. Поведение unchanged (🎁 icon с unread badge → click → modal).
- **`UpdateBanner.tsx`** — replaced `window.open(DOCS_URL)` на
  `setWhatsNewOpen(true)` + render `<WhatsNewModal>`. DOCS_URL constant
  removed (больше не используется).

## UX intent

Когда update detected:
1. User видит orange banner «Доступна новая версия v2.16.0»
2. Hover «Что нового» button → opens modal
3. User читает release notes (что добавлено, исправлено) ДО reload
4. Click «Обновить» → reload

Раньше «Что нового» вёл к generic docs page — user не знал ЧТО
изменилось, только что update есть. Modal даёт structured answer.

## Test plan

- [x] `pnpm tsc --noEmit` clean
- [x] `pnpm build` clean
- [x] `pnpm vitest run useChangelog.test` — 11/11 pass (parser logic
      unchanged)
- [ ] Manual: симулировать update banner (mock useAppVersion
      `updateAvailable=true`), click «Что нового» → modal opens
- [ ] Manual: 🎁 button в TopBar также opens same modal
- [ ] Manual: close modal → red dot disappears на 🎁 button
      (localStorage updated)
2026-05-12 23:28:12 +03:00
semantic-release-bot 6c48cfac46 chore(release): v2.15.0 [skip ci]
## [2.15.0](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/compare/v2.14.0...v2.15.0) (2026-05-12)

###  Features

* **topbar:** «Что нового» button + 2026-05-12 state.md update ([a34c6d4](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/a34c6d4c8248040f90fc24597317d748195468b5))

### 🐛 Fixes

* **admin-ui:** changelog build context — copy script + CI before_script ([5103ed6](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/5103ed6d9cd8e5ba742f66ea72fa8f35e1dcf60a))
* **docs,security:** Swagger UI — staging для exploration, prod env-flagged ([e2080d8](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/e2080d899153f78eb35cf7fab7ea59d52fdc151d))
* **docs:** copy operator/reviewer/changelog dirs to docker image ([82e481a](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/82e481a418f278a566bf6cc3ab7345c99e363902))

### 📝 Docs

* **design:** AI schema assist + dictionary marketplace v1 proposals ([cef5f49](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/cef5f49bf70d5abf857e4d40bfb5550a751fe2ec))
* **design:** ai-schema-assist v2 — /office-hours, Approach C hybrid ([a3eb5f0](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/a3eb5f0c86196acf3fbbad0e5ac74eb058eaa7e3))
* **design:** marketplace v2 — bundle hygiene, hard cap 2-3d ([89c6e04](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/89c6e049657db808ff7c7fa70c58c3311360673a))
* **design:** Redis FK projection v2 — eng review applied ([d9fc49f](https://git.nstart.cloud/2-6/2-6-4/terravault/ordinis/commit/d9fc49fe72cfcf4cbb7b7568aba560a5a3e1dc84))
2026-05-12 18:54:51 +00:00
Александр Зимин 013749e0e8 Merge branch 'fix/changelog-build-context' into 'main'
fix(admin-ui): changelog build context — copy script + CI before_script

See merge request 2-6/2-6-4/terravault/ordinis!151
2026-05-12 18:48:04 +00:00
Александр Зимин 5103ed6d9c fix(admin-ui): changelog build context — copy script + CI before_script 2026-05-12 18:48:04 +00:00