Skip to content

docs: add ROADMAP.md, TZ.md, and AGENT_QUICKSTART.md for agent handoff#617

Merged
barach6662001-bit merged 2 commits intomainfrom
docs/roadmap-and-tz
Apr 24, 2026
Merged

docs: add ROADMAP.md, TZ.md, and AGENT_QUICKSTART.md for agent handoff#617
barach6662001-bit merged 2 commits intomainfrom
docs/roadmap-and-tz

Conversation

@barach6662001-bit
Copy link
Copy Markdown
Owner

Captures completed work across PR #602-611, remaining scope across PR #612-617, locked architectural decisions, technical debt, and agent protocol.

Captures completed work across PR #602-611, remaining scope across PR #612-617,
locked architectural decisions, technical debt, and agent protocol.
@chatgpt-codex-connector
Copy link
Copy Markdown

💡 Codex Review

- [x] **PR #607**`/api/tenant/data-boundaries`, `‹ ›` arrow stepping with keyboard, `<TotalCard>` shared component *(TZ 2 partial, TZ 6 partial)*
- [x] **PR #609** — tenant feature flags, `[RequireFeatureFlag]` middleware, `FeatureFlagGate`, Budget hidden by flag *(TZ 4, TZ 5)*

P1 Badge Include PR #608 in completed roadmap history

The new Completed list jumps from PR #607 to PR #609, but the repository history already contains merged PR #608; this directly conflicts with the quickstart instruction to verify git log --oneline -20 against ROADMAP.md and will make that validation fail even when the repo is healthy. In practice this can block handoff or cause agents to misclassify shipped work as missing, so the completed sequence should include #608 (or the verification rule should be narrowed).


- PR #615 — tie `SeasonId` FK onto AgroOperation / CostRecord / Sale (depends on #612). Backfill by date-range.

P2 Badge Align PR #615 scope in plan.md with roadmap

docs/plan.md is still labeled as an active roadmap, but its PR #615 item is now SeasonId FK backfill, which conflicts with the newly added canonical roadmap/TZ documents where PR #615 is warehouse/inventory scope. If someone follows plan.md (or references it during handoff), they can start the wrong PR and break the enforced sequencing, so this entry should be synchronized or the file clearly marked deprecated.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@barach6662001-bit barach6662001-bit merged commit 32f22f3 into main Apr 24, 2026
3 checks passed
@barach6662001-bit barach6662001-bit deleted the docs/roadmap-and-tz branch April 24, 2026 09:10
barach6662001-bit added a commit that referenced this pull request Apr 24, 2026
barach6662001-bit added a commit that referenced this pull request Apr 25, 2026
…640)

* docs(roadmap): correct currency conversion v2 PR number (#632#634) and note switcher is re-enabled

* chore(roadmap): split PR #614 into core + #614a (system) + #614b (catalogs) + #614c (broadcast)

PR #614 scope as a single ticket was too broad (3 admin pages + impersonation
engine + audit log + global users + 4 sub-features each non-trivial). Split
into a sequenced set so each is reviewable:

- #614 (in progress): impersonation engine + /admin/users + /admin/audit-log
  + 6 integration tests + red banner + rate limit + forbidden-action filter
- #614a: /admin/system read-only health dashboard + audit-log CSV export
- #614b: /admin/catalogs (global reference data CRUD)
- #614c: /admin/broadcast (depends on PR #617 notifications fixes)

TZ.md status marker updated to reflect the split.

* feat(impersonation): backend engine, JWT extension, forbidden-action filter, /admin/users + audit-log filters

- ICurrentUserService gains IsImpersonating + ImpersonatedByUserId
- JwtTokenService.GenerateImpersonationToken issues a 60min token with claims
  is_super_admin=false, impersonated_by_user_id, original_tenant_id, impersonation_reason
- IImpersonationService + ImpersonationService:
  * StartAsync: validates target (not self, not super-admin, active), enforces 3/24h
    rate limit by querying SuperAdminAuditLog, audits 'impersonate.start',
    inserts in-app Notification (severity warning, body in Ukrainian with Kyiv
    timestamp + reason), best-effort email
  * EndAsync: requires impersonated_by_user_id claim, audits 'impersonate.end',
    re-issues a fresh super-admin token for the original admin
  * LogForbiddenAttemptAsync: writes 'impersonate.forbidden_attempt' audit row
- ForbiddenDuringImpersonationAttribute (filter): on 403 also writes the
  forbidden-attempt audit row with the attempted route. Applied to:
  AuthController.ChangePassword, ApiKeysController create + revoke,
  UsersController.UpdateUserRole + ResetUserPassword
- ImpersonationController: POST /api/admin/impersonate (super-admin only),
  POST /api/admin/impersonate/end (callable from impersonation token)
- AdminController: GET /api/admin/users (global search across tenants with
  tenant name), GET /api/admin/audit-log gains action / adminUserId / fromUtc /
  toUtc / tenantId filters

* feat(impersonation): add partial composite index for rate-limit query

CREATE INDEX ix_superadminauditlogs_impersonation_ratelimit
  ON SuperAdminAuditLogs (AdminUserId, TargetId, OccurredAt DESC)
  WHERE Action = 'impersonate.start'

Backs the 3/24h-per-(admin,target) rate-limit lookup in
ImpersonationService.StartAsync. Partial filter keeps the index small as
the audit log grows across all super-admin actions.

* test(impersonation): 6 integration scenarios + extend TestAuthHandler with X-Test-ImpersonatedBy

- (1) non-super-admin caller -> 403
- (2) super-admin without MFA -> 403 + X-Mfa-Required
- (3) happy path -> 200 + 60min token + audit row + Notification row (Ukrainian
      title 'Сесія імперсонації', warning severity, body contains the reason)
- (4) reason < 10 chars -> 400
- (5) rate limit: 4th impersonation in 24h on same target -> 429
      (3 prior 'impersonate.start' rows pre-seeded directly into
      SuperAdminAuditLogs to keep the test deterministic and fast)
- (6) forbidden action under impersonation token (POST /api/auth/change-password)
      -> 403 AND writes a separate 'impersonate.forbidden_attempt' audit row

TestAuthHandler now also accepts X-Test-ImpersonatedBy for forbidden-action
coverage, matching the JWT contract (impersonation tokens always carry
is_super_admin=false).

All 441 tests pass (315 unit + 126 integration).

* feat(frontend): super-admin impersonation banner + /admin/users + /admin/audit-log

- authStore: add impersonation slice (target + original snapshots, expires)
- api/admin: startImpersonation / endImpersonation / listAdminUsers / listAdminAuditLog
- ImpersonationBanner: red, non-closable, z-index 9999, full viewport, countdown, exit restores original token
- /admin/users: global users table with reason modal (>=10 chars) + impersonate
- /admin/audit-log: filtered audit log with expandable payload
- i18n keys (uk + en) for users page, audit log, banner, modal

* fix(impersonation): default-impl IsImpersonating/ImpersonatedByUserId on ICurrentUserService

Avoids breaking 5 unit-test stubs (FakeCurrentUserService + per-test TestCurrentUserService) without forcing them to update.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant