diff --git a/README.md b/README.md index 6fd2831..4ec7bbd 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,9 @@ Stock dividends are manual portfolio adjustments: - `/stock_cash_dividend ` credits a positive whole-VND amount for each pre-event share held. Eg: `/stock_cash_dividend 1500 TCB`. - `/stock_share_dividend ` adds `floor(pre_event_shares × new / owned)` whole shares. Eg: `/stock_share_dividend 100:10 TCB`. -- `/stock_dividend ` applies both parts from the same pre-event holding and saves them together. Eg: `/stock_dividend 1500 100:10 TCB`. + +The combined `/stock_dividend` shortcut was retired. Use the specialized cash +and share commands above for new adjustments. Ratios use `owned:new` exactly as written in the issuer notice. Equivalent unreduced ratios are accepted and the entered ratio is preserved in the reply. diff --git a/docs/deploy-coolify-selfhosted.md b/docs/deploy-coolify-selfhosted.md index 1fac3af..116d28e 100644 --- a/docs/deploy-coolify-selfhosted.md +++ b/docs/deploy-coolify-selfhosted.md @@ -100,7 +100,9 @@ Successful GIF replies include the result behind Telegram spoiler formatting. > The `stats` collection uses queryable aggregate documents for command/user > counts and creates indexes on startup. Deleted legacy command rows are > retained with `deleted: true`; `/stats` queries filter those rows from visible -> results. A historical `system` collection may remain in MongoDB with completed +> results. Stats startup uses the idempotent +> `migration:stats-delete-stock-dividend-v1` migration to retire historical +> `/stock_dividend` rows without erasing them. A historical `system` collection may remain in MongoDB with completed > migration records; keep those records as audit history. Stock stores cash as > `vnd`, embeds positions as `assets..{quantity,base,openedAt}`, and > retains normalized per-user SSI history under diff --git a/docs/journals/260722-1843-retire-stock-dividend-command.md b/docs/journals/260722-1843-retire-stock-dividend-command.md new file mode 100644 index 0000000..577b193 --- /dev/null +++ b/docs/journals/260722-1843-retire-stock-dividend-command.md @@ -0,0 +1,63 @@ +--- +title: Retire Redundant Stock Dividend Command +date: 2026-07-22 18:43 +component: stock module +status: completed +--- + +# Retire Redundant Stock Dividend Command + +## Context + +The stock module had one command too many. `/stock_dividend` overlapped with the specialized cash and share dividend commands, and the combined path was doing more harm than good. It blurred intent, made command handling harder to reason about, and created extra surface area for bugs that did not buy us any actual user value. + +## What Happened + +We removed the redundant `/stock_dividend` command and kept the specialized `/stock_cash_dividend` and `/stock_share_dividend` flows. That decision matches the product behavior: cash and share dividends are distinct operations, and forcing them through one combined command was an abstraction tax with no payoff. + +Historical stats were not deleted. They were marked `deleted:true` so the record stays intact for analytics and compatibility while the visible views stop treating the command as active. That is the right compromise for a retired command: preserve history, suppress the live surface. + +We also fixed two implementation traps that showed up while wiring the removal. First, the rolling deploy finding made it clear that a one-time migration was not enough; read/write suppression and every-boot reconciliation are now permanent so mixed-version deployments do not resurrect a dead command. Second, the Mongo path had an N+1 shape, so we replaced the row-by-row updates with `UpdateMany` plus `CountDocuments` instead of pretending the loop was acceptable. + +## Brutal Truth + +This was one of those changes where the codebase was telling us the truth before we were willing to hear it. Keeping a redundant command around because it “might be convenient” would have meant carrying dead complexity forever. The frustrating part is that the bug shape was predictable: once the command surface drifted from the underlying model, every extra path became another place to desync. + +## Technical Details + +- `/stock_dividend` removed from the live command surface +- `/stock_cash_dividend` and `/stock_share_dividend` retained as the explicit paths +- legacy stats rows preserved with `deleted:true` +- exact-prefix matching kept for safety so we do not suppress unrelated command names +- Mongo writes changed from per-document updates to `UpdateMany` plus `CountDocuments` + +## What We Tried + +- Considered leaving `/stock_dividend` as a compatibility alias, but that kept the ambiguity alive. +- Considered deleting stats history, but that would have broken reporting and destroyed useful audit data. +- Considered a row-by-row Mongo rewrite, but the N+1 behavior was the same old inefficiency in a different coat. + +## Root Cause Analysis + +The root cause was command overloading. We tried to make one command cover two different user intents, and the result was a brittle contract that was harder to maintain than the feature justified. The deploy/reconciliation issue was the same class of mistake: assuming a one-time action would stay correct across rolling versions. It will not. + +## Lessons Learned + +- Prefer explicit commands when the operations are semantically different. +- Retain legacy stats, but mark retired commands as deleted instead of pretending they never existed. +- Exact-prefix checks matter when suppressing command behavior; sloppy matching is how unrelated names get caught in the blast radius. +- If a deploy has mixed versions, assume the old behavior will reappear unless read/write suppression and reconciliation are both permanent. + +## Verification + +- focused stock, stats, and server tests passed +- focused race-enabled tests passed +- full `go test -count=1 ./...` passed +- real MongoDB 8 integration ran through Testcontainers and passed +- `go build ./...` and `go vet ./...` passed +- `golangci-lint run` reported zero issues +- total statement coverage measured `77.3%`; the repository has no configured coverage threshold + +## Next Steps + +The code is done and verified. The only remaining step is a commit, pending user approval. After that, the retired-command behavior should be watched in real deployments for any command-menu or stats edge cases. diff --git a/plans/260722-1843-retire-stock-dividend-command/phase-01-remove-command-and-retire-stats.md b/plans/260722-1843-retire-stock-dividend-command/phase-01-remove-command-and-retire-stats.md new file mode 100644 index 0000000..ca6a655 --- /dev/null +++ b/plans/260722-1843-retire-stock-dividend-command/phase-01-remove-command-and-retire-stats.md @@ -0,0 +1,50 @@ +--- +phase: 1 +title: Remove Command and Retire Stats +status: completed +priority: P1 +dependencies: [] +effort: medium +--- + +# Phase 1: Remove Command and Retire Stats + +## Overview + +Delete the generic stock dividend command and add durable stats retirement for +its retained usage history. + +## Requirements + +- Preserve `/stock_cash_dividend` and `/stock_share_dividend` behavior. +- Remove all active `/stock_dividend` command surfaces. +- Retain stats rows rather than deleting usage history. +- Filter retired rows from every memory and Mongo stats view. +- Reconcile late legacy writes on future startup runs. +- Keep Mongo startup bounded with exact-command bulk operations. + +## Architecture + +The stats runtime owns a permanent retired-command rule. New increments for the +retired command are ignored and reads exclude it independently of the stored +flag. Startup physically restores `deleted: true`: memory uses versioned CAS; +Mongo uses indexed `UpdateMany` plus `CountDocuments`. The shared system marker +stores completion status and the current exact-command row count. + +## Implementation Checklist + +- [x] Remove command registration and combined handler. +- [x] Remove command-menu metadata and handler/menu tests. +- [x] Remove README command documentation. +- [x] Add stats/system startup wiring and migration marker. +- [x] Add permanent read/write retirement behavior. +- [x] Add memory and real-Mongo reconciliation tests. +- [x] Pass review, test, race, build, vet, lint, and diff gates. + +## Risks + +- A legacy process can write after migration; runtime filtering hides it and + every later startup physically tombstones it again. +- Unbounded per-row Mongo startup work is avoided by exact indexed bulk calls. +- Marker count is derived from all matching rows, so retries do not undercount. + diff --git a/plans/260722-1843-retire-stock-dividend-command/plan.md b/plans/260722-1843-retire-stock-dividend-command/plan.md new file mode 100644 index 0000000..775f3ac --- /dev/null +++ b/plans/260722-1843-retire-stock-dividend-command/plan.md @@ -0,0 +1,44 @@ +--- +title: Retire Generic Stock Dividend Command +description: Remove /stock_dividend while preserving and hiding historical usage statistics. +status: completed +priority: P1 +effort: medium +branch: main +tags: + - stock + - stats + - migration + - telegram +created: 2026-07-22 +--- + +# Retire Generic Stock Dividend Command + +## Overview + +Remove the combined manual dividend command because users can apply cash and +share dividends through the two specialized commands. Retain historical stats +as deleted legacy records and prevent rolling-deploy writes from exposing them. + +## Phases + +| Phase | Name | Status | Progress | +|---|---|---|---| +| 1 | [Remove Command and Retire Stats](./phase-01-remove-command-and-retire-stats.md) | Completed | 7/7 (100%) | + +## Dependencies + +- Uses the existing stats `deleted` field and shared `system` collection. +- No portfolio schema or dividend-history migration changes. + +## Success Criteria + +- [x] `/stock_dividend` is absent from registration, menu, help, handler, and README. +- [x] Cash and share dividend commands remain available and unchanged. +- [x] Anonymous and per-user historical rows remain stored with `deleted: true`. +- [x] Retired rows remain hidden even after a late legacy-process write. +- [x] Memory reconciliation is idempotent and Mongo reconciliation is bulk/indexed. +- [x] Exact-prefix commands such as `stock_dividend_extra` remain untouched. +- [x] Full, race, Mongo integration, build, vet, and lint checks pass. + diff --git a/plans/260722-1843-retire-stock-dividend-command/reports/pm-260722-1843-retire-stock-dividend-command.md b/plans/260722-1843-retire-stock-dividend-command/reports/pm-260722-1843-retire-stock-dividend-command.md new file mode 100644 index 0000000..b425ada --- /dev/null +++ b/plans/260722-1843-retire-stock-dividend-command/reports/pm-260722-1843-retire-stock-dividend-command.md @@ -0,0 +1,39 @@ +--- +title: Retire Stock Dividend Command Completion Report +status: completed +created: 2026-07-22 +--- + +# Retire Stock Dividend Command Completion Report + +## Summary + +| Metric | Result | +|---|---| +| Phases | 1/1 completed | +| Tasks | 7/7 completed | +| Code review | Pass; no remaining findings | +| Focused/full tests | Pass | +| Race tests | Pass | +| Mongo 8 integration | Pass; executed via Testcontainers | +| Build / vet / lint | Pass / pass / 0 issues | +| Statement coverage | 77.3% | + +## Achievements + +- Removed the redundant combined dividend command from every active surface. +- Preserved specialized cash/share dividend workflows. +- Retained historical stats while permanently suppressing the retired command. +- Hardened rolling-deploy behavior against late legacy increments. +- Used bounded Mongo bulk reconciliation with exact-prefix safety. + +## Known Limitations + +- A still-running legacy binary can show its own stale stats view until it is + replaced; new binaries suppress the command immediately. +- No repository coverage threshold is configured; measured total is 77.3%. + +## Next Step + +- Commit and push after user approval. +