[Bug] Обновлять сохранённые payout calendars в hourly pass #53

Closed
opened 2026-10-11 21:41:56 +03:00 by codex-bot · 2 comments
Owner

Problem

events.refresh→readMany→requestMany(force=false) переиспользует stable dividends/bond-events. Bond key постоянный; dividend key обновляется при смене окна, но intraday corrections подавлены. Missing MOEX block трактуется как malformed вопреки spec; верная provider semantics неизвестна.

Expected Behavior

Scheduled pass получает изменения календаря, сохраняет previous complete calendar/forecast при failure и не force-читает unrelated metadata/quotes/history.

Evidence

Audit date: 2026-10-11. Reports: 06, 07, 08. Finding IDs: BE-003, BE-014, FLOW-006, RISK-004, TQ-006, TA-F03, TA-F14. Verified revision: 0ad84ff4f0b4712abc7e69dc299856f14af6eadd; Confidence: High для reuse/conflict, Medium provider absence. Verification status: static; missing-block runtime-unverified.
Affected code: upcoming-events.service.ts:182–239; catalog-enrichment.service.ts:113–125,184–194,679–692; moex.client.ts:119–198.

Scope

In scope

Calendar-specific existing force/freshness, shared UID coalescing и named missing/empty/malformed cases.

Out of scope

Whole-source force, удаление shared cache/FK, ослабление malformed/cursor guards, live financial calls.

Proposed Approach

Применить current force только календарям. Missing-block трактовку менять лишь по primary contract/обезличенному offline fixture; conservative failure сохраняется до решения.

Acceptance Criteria

  • Второй pass того же UID/window с changed/cancelled payout меняет source revision/events.
  • Failed second refresh сохраняет first complete rows/time/forecast и observable failure.
  • Два accounts с одним UID не дублируют одну намеренную календарную загрузку внутри pass.
  • Local GET не вызывает calendar RPC; stable detail reuse и page completeness сохранены.
  • Missing/empty/malformed/cursor semantics разделены либо явно принято оставить conservative missing failure с согласованной spec.

Verification

Existing upcoming-events SQLite/real-parser cases и moex.client.spec.ts; actual scheduled source path вместо scheduler stub. Relevant backend gates на конечном diff.

Dependencies

backend #55: failed-existing-calendar publication/fallback proof до force. Native dependency установлена и прочитана обратно.

Traceability

Audit campaign: WealthLedger/main#23 . Audit findings: BE-003, BE-014, FLOW-006, RISK-004, TQ-006, TA-F03, TA-F14. Repository: WealthLedger/backend; T04.

<!-- wealthledger-audit:2026-10-11:calendar-refresh --> ## Problem events.refresh→readMany→requestMany(force=false) переиспользует stable dividends/bond-events. Bond key постоянный; dividend key обновляется при смене окна, но intraday corrections подавлены. Missing MOEX block трактуется как malformed вопреки spec; верная provider semantics неизвестна. ## Expected Behavior Scheduled pass получает изменения календаря, сохраняет previous complete calendar/forecast при failure и не force-читает unrelated metadata/quotes/history. ## Evidence Audit date: `2026-10-11`. Reports: [06](https://git.personal.ksv741.ru/WealthLedger/main/src/commit/d07fbfabb1295780952a554185989d2d5b5c8349/audits/2026-10-11/reports/06-backend-architecture-report.md), [07](https://git.personal.ksv741.ru/WealthLedger/main/src/commit/d07fbfabb1295780952a554185989d2d5b5c8349/audits/2026-10-11/reports/07-api-and-data-flow-efficiency-report.md), [08](https://git.personal.ksv741.ru/WealthLedger/main/src/commit/d07fbfabb1295780952a554185989d2d5b5c8349/audits/2026-10-11/reports/08-test-quality-and-effectiveness-report.md). Finding IDs: BE-003, BE-014, FLOW-006, RISK-004, TQ-006, TA-F03, TA-F14. Verified revision: `0ad84ff4f0b4712abc7e69dc299856f14af6eadd`; Confidence: High для reuse/conflict, Medium provider absence. Verification status: static; missing-block runtime-unverified. Affected code: [upcoming-events.service.ts:182–239](https://git.personal.ksv741.ru/WealthLedger/backend/src/commit/0ad84ff4f0b4712abc7e69dc299856f14af6eadd/src/modules/investments/upcoming-events/upcoming-events.service.ts#L182-L239); catalog-enrichment.service.ts:113–125,184–194,679–692; moex.client.ts:119–198. ## Scope ### In scope Calendar-specific existing force/freshness, shared UID coalescing и named missing/empty/malformed cases. ### Out of scope Whole-source force, удаление shared cache/FK, ослабление malformed/cursor guards, live financial calls. ## Proposed Approach Применить current force только календарям. Missing-block трактовку менять лишь по primary contract/обезличенному offline fixture; conservative failure сохраняется до решения. ## Acceptance Criteria - [x] Второй pass того же UID/window с changed/cancelled payout меняет source revision/events. - [x] Failed second refresh сохраняет first complete rows/time/forecast и observable failure. - [x] Два accounts с одним UID не дублируют одну намеренную календарную загрузку внутри pass. - [x] Local GET не вызывает calendar RPC; stable detail reuse и page completeness сохранены. - [x] Missing/empty/malformed/cursor semantics разделены либо явно принято оставить conservative missing failure с согласованной spec. ## Verification Existing upcoming-events SQLite/real-parser cases и moex.client.spec.ts; actual scheduled source path вместо scheduler stub. Relevant backend gates на конечном diff. ## Dependencies [backend #55](https://git.personal.ksv741.ru/WealthLedger/backend/issues/55): failed-existing-calendar publication/fallback proof до force. Native dependency установлена и прочитана обратно. ## Traceability Audit campaign: https://git.personal.ksv741.ru/WealthLedger/main/issues/23 . Audit findings: BE-003, BE-014, FLOW-006, RISK-004, TQ-006, TA-F03, TA-F14. Repository: WealthLedger/backend; T04.
codex-bot added this to the Technical Audit Remediation — 2026-10-11 milestone 2026-10-11 21:41:56 +03:00
codex-bot added the Kind/Bug
Priority
Medium
3
audit/2026-10-11
labels 2026-10-11 21:41:56 +03:00
codex-bot was assigned by hermes-agent 2026-10-12 00:04:26 +03:00
hermes-agent added this to the WealthLedger — Delivery Board project 2026-10-12 00:04:26 +03:00
Author
Owner

Merged: PR #157 (squash, develop aeb4647). CI (commits, security, verify) was green before merge.

Decisions (owner)

  • MOEX response without the requested block, or with malformed block/cursor: keep the conservative failure (MOEX_UNAVAILABLE); the previous complete calendar/forecast is kept. Confirmed empty block and 404 "not found" stay a successful "no data". moex.client.ts and moex.client.spec.ts unchanged.
  • Spec aligned separately: WealthLedger/specs#76, PR WealthLedger/specs#77 (scenario "MOEX отвечает без запрошенного блока" now describes a source failure; new scenario for confirmed empty/not found).

Change

  • sourcePolicy: dividends and bond-events get policy calendar (was stable).
  • CatalogEnrichmentService.readMany/requestMany take optional refreshBefore; a calendar is re-read only if fetchedAt < refreshBefore. Without it behaviour is unchanged (GET, stock/bond income, candles, discovery).
  • UpcomingEventsService.refresh takes walkStartedAt; TbankHourlyScheduler.dispatchEvents passes the walk start (same as portfolio). A second account with the same UID sees the fresh fetchedAt and skips the load.
  • Detail/metadata/quotes/history are not forced. A failed refresh does not overwrite the row, so the previous complete calendar and forecast stay (boundary from #55).
  • No migration, no schema change, no API/OpenAPI change.

Evidence

  • 4 new tests in test/upcoming-events.e2e-spec.ts (SQLite HTTP, real refresh→readMany→requestMany path, scheduler not stubbed) failed on the old code; 30/30 pass after the fix. Cases: changed then cancelled payout in the same window bumps dataRevision and events; failed refresh keeps row, fetchedAt, lastSuccessfulAt, forecast and shows failure; two accounts with the same UID = one load per walk; GET and a plain refresh() without a walk stamp make no calendar RPC and detail is not re-read; bond calendar with a constant key is re-read only on a walk pass.
  • npm run verify: 42 suites / 310 tests, lint, typecheck, build — OK. npm run test:e2e (all SQLite suites): exit 0. npm run openapi:check: OK.

Not verified / limits

  • No runtime check against real T-Bank/MOEX (live calls forbidden), no PostgreSQL. MOEX provider behaviour on a missing block was not confirmed.
  • Load: each hourly walk now makes one MOEX request set per distinct bond UID (previously none after the first load). Not measured.
  • AC 5 is left unchecked until specs PR #77 is merged (the "agreed spec" part of the criterion); the code side (conservative failure kept) is done.

API contract

No change.

Merged: PR #157 (squash, develop `aeb4647`). CI (commits, security, verify) was green before merge. ## Decisions (owner) - MOEX response without the requested block, or with malformed block/cursor: keep the conservative failure (`MOEX_UNAVAILABLE`); the previous complete calendar/forecast is kept. Confirmed empty block and 404 "not found" stay a successful "no data". `moex.client.ts` and `moex.client.spec.ts` unchanged. - Spec aligned separately: WealthLedger/specs#76, PR WealthLedger/specs#77 (scenario "MOEX отвечает без запрошенного блока" now describes a source failure; new scenario for confirmed empty/not found). ## Change - `sourcePolicy`: `dividends` and `bond-events` get policy `calendar` (was `stable`). - `CatalogEnrichmentService.readMany/requestMany` take optional `refreshBefore`; a calendar is re-read only if `fetchedAt < refreshBefore`. Without it behaviour is unchanged (GET, stock/bond income, candles, discovery). - `UpcomingEventsService.refresh` takes `walkStartedAt`; `TbankHourlyScheduler.dispatchEvents` passes the walk start (same as portfolio). A second account with the same UID sees the fresh `fetchedAt` and skips the load. - Detail/metadata/quotes/history are not forced. A failed refresh does not overwrite the row, so the previous complete calendar and forecast stay (boundary from #55). - No migration, no schema change, no API/OpenAPI change. ## Evidence - 4 new tests in `test/upcoming-events.e2e-spec.ts` (SQLite HTTP, real `refresh→readMany→requestMany` path, scheduler not stubbed) failed on the old code; 30/30 pass after the fix. Cases: changed then cancelled payout in the same window bumps `dataRevision` and events; failed refresh keeps row, `fetchedAt`, `lastSuccessfulAt`, forecast and shows failure; two accounts with the same UID = one load per walk; GET and a plain `refresh()` without a walk stamp make no calendar RPC and detail is not re-read; bond calendar with a constant key is re-read only on a walk pass. - `npm run verify`: 42 suites / 310 tests, lint, typecheck, build — OK. `npm run test:e2e` (all SQLite suites): exit 0. `npm run openapi:check`: OK. ## Not verified / limits - No runtime check against real T-Bank/MOEX (live calls forbidden), no PostgreSQL. MOEX provider behaviour on a missing block was not confirmed. - Load: each hourly walk now makes one MOEX request set per distinct bond UID (previously none after the first load). Not measured. - AC 5 is left unchecked until specs PR #77 is merged (the "agreed spec" part of the criterion); the code side (conservative failure kept) is done. ## API contract No change.
Author
Owner

AC 5 closed: spec aligned and merged — WealthLedger/specs#77 (develop aa1a57a, Issue specs#76). Scenario "MOEX отвечает без запрошенного блока" now describes a source failure (missing/malformed block or cursor; previous complete calendar/forecast kept, meta shows the failed attempt); new scenario for confirmed empty block / 404 = successful "no data". Spec now matches MoexClient and moex.client.spec.ts. All five AC are checked. Still not verified in runtime: real MOEX behaviour on a missing block (live calls forbidden).

AC 5 closed: spec aligned and merged — WealthLedger/specs#77 (develop `aa1a57a`, Issue specs#76). Scenario "MOEX отвечает без запрошенного блока" now describes a source failure (missing/malformed block or cursor; previous complete calendar/forecast kept, `meta` shows the failed attempt); new scenario for confirmed empty block / 404 = successful "no data". Spec now matches `MoexClient` and `moex.client.spec.ts`. All five AC are checked. Still not verified in runtime: real MOEX behaviour on a missing block (live calls forbidden).
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: WealthLedger/backend#53