[Bug] Публиковать новый полный forecast независимо от request window #55
Notifications
Due Date
No due date set.
Blocks
#63 [Investigation] Согласовать Events screen contract и заменить устаревшие oracles
WealthLedger/frontend
#53 [Bug] Обновлять сохранённые payout calendars в hourly pass
WealthLedger/backend
Reference: WealthLedger/backend#55
Reference in New Issue
Block a user
Problem
resolve возвращает первый overlapping fallback до validation/upsert нового complete candidate. Daily window shift может удерживать прежние amounts. Disjoint saved-read возвращает null вместо empty filtered result. GET и scheduled producer делят publication; replacement после failed existing calendar пока runtime-unverified.
Expected Behavior
Новый согласованный полный forecast становится latest; фильтрующий GET сохраняет original calculatedAt/window/identity. Failed/partial update сохраняет previous complete result.
Evidence
Audit date:
2026-10-11. Reports: 06, 08. Finding IDs: BE-001, BE-013, ARCH-008, CPLX-004, FLOW-007, RISK-003, TQ-005, TA-F01.Verified revision:
0ad84ff4f0b4712abc7e69dc299856f14af6eadd; Confidence: High (selection), Medium (failed-source GET policy). Verification status: static / runtime-unverified для BE-013.Affected code: forecast.repository.ts:50–175; forecast.service.ts:77–123; upcoming-events.service.ts:152–173,271–299; test/upcoming-events.e2e-spec.ts:321–339,619–641.
Scope
In scope
Deterministic latest selection, producer/read distinction, account/all semantics, original-window wire artifact и все три ForecastService consumers.
Out of scope
Удаление history/old rows, ослабление dependency CAS, CQRS/new ForecastModule, guessed product policy.
Proposed Approach
Сначала controlled same-UID quantity change + failed existing calendar + normal GET: зафиксировать допустимую snapshot policy. Разделить selection/publication/filter у existing owners; additive transition только если меняется persisted key. Одного удаления early return недостаточно.
Acceptance Criteria
Verification
Existing upcoming-events isolated SQLite/HTTP cases: shifted windows, disjoint/overlap, failure и dependency races. Existing narrow timestamp proof оставить. Runtime OpenAPI parity, frontend consumer check при artifact delivery; полный relevant verify на конечном diff.
Dependencies
Блокирующих Issues нет. Calendar force T04 использует принятую здесь failure/publication boundary.
Traceability
Campaign: WealthLedger/main#23 . Findings: BE-001, BE-013, ARCH-008, CPLX-004, FLOW-007, RISK-003, TQ-005, TA-F01. Repository: WealthLedger/backend; T02.
Merged: PR #156 (squash, develop
169bee1). CI (commits, security, verify) was green before merge.Decisions (owner, BE-013)
meta(failed/partial,lastAttempt); the new candidate is not published, no mixed basis.from/to). Explicitfrom/toreads and filters the latest forecast; it publishes only if nothing is saved yet.Change
ForecastRepository.resolve: removed the early return of the first overlapping fallback before candidate validation/upsert. Latest =calculatedAt desc, scopeKey ascwithin account + current membership revision. Disjoint range returns a successful empty result with the originalcalculatedAt/window.ForecastService: response carries the saved forecast's original window.scopeKeychange, no migration, old rows untouched.ForecastServiceconsumersstock/dividendsandbond/couponsuse exact-key rows and were not modified.Evidence
test/upcoming-events.e2e-spec.ts(SQLite HTTP): 5 of 26 failed on the old code, 26/26 pass after the fix. Cases: W0→W1 with changed quantity (stale and disjoint explicit ranges serve W1), narrow explicit range does not replace the wide forecast, failed new-holding calendar after window shift keeps the previous forecast, shifted window published without dependencies, disjoint range = empty result instead ofnull.npm run verify: lint, typecheck app+test, 42 suites / 310 tests, build — OK.npm run test:e2e(all SQLite suites): exit 0.npm run openapi:generate: schema containsUpcomingEventsDto.window(openapi.jsonis gitignored).Not verified / limits
refuses a calculated snapshot if its portfolio changes…,discards a late calendar from a replaced connection…, per-currency totals) which still pass; no new race tests were added.API contract change (backend → frontend)
Additive:
UpcomingEventsDto.window: { from: 'YYYY-MM-DD', to: 'YYYY-MM-DD' }(required in the response,GET /brokerage-accounts/{id|all}/events). The current frontend is unaffected; types must be regenerated from the new OpenAPI artifact, no hand edits.AC 5 closed; Issue closed. All five AC are checked.
Window artifact delivered to the frontend
aaee959, CI green):src/shared/api/spec/openapi.jsonreplaced by the backend OpenAPI document (only differences from the previous snapshot:UpcomingEventsDto.properties.window, itsrequiredentry, new schemaUpcomingEventsWindowDto {from, to}; paths identical).schema.d.tsproduced bynpm run openapi:generate(+8 lines), not edited by hand. Frontendnpm run verify(74 files / 553 tests, knip, depcruise, build, storybook build): exit 0.windowis not displayed yet. Showing it and the filters decision belong to WealthLedger/frontend#63.Account/all, stock dividends, bond coupons
Covered by pre-existing SQLite HTTP cases in
test/upcoming-events.e2e-spec.ts, which pass unchanged (suite 30/30, fullnpm run test:e2eexit 0): "preserves source identity, inclusive dates, exact currencies, cancellation and offers in account/all scopes", "serves instrument payouts from shared calendars with private exact holdings…" (stock dividends) and thestocks/bondspayout-position cases (bond coupons). The consumersStockIncomeService/BondIncomeServiceuse exact-key rows and were not changed in #156. No new dedicated tests were added for them.Not verified / limits
API contract
Additive
UpcomingEventsDto.window: {from, to}(required in the response); frontend snapshot synchronized as above.