diff --git a/README.md b/README.md index 49244ba..a214eff 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,20 @@ Atlas via long polling and an in-process cron scheduler. Disable modules with the `MODULES` environment variable. +### Stock dividend commands + +Stock dividends are manual portfolio adjustments: + +- `/stock_cash_dividend ` credits a positive whole-VND amount for each pre-event share held. Example: `/stock_cash_dividend 1500 TCB`. +- `/stock_share_dividend ` adds `floor(pre_event_shares × new / owned)` whole shares. Example: `/stock_share_dividend 100:10 TCB`. +- `/stock_dividend ` applies both parts from the same pre-event holding and saves them together. Example: `/stock_dividend 1500 100:10 TCB`. + +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. +The bot validates syntax, tickers, and arithmetic safety, but does not look up +notices or prevent duplicate calls. The caller is responsible for verifying the +notice and avoiding accidental repeated adjustments. + ## Layout ``` diff --git a/docs/journals/260720-1612-stock-dividend-command-naming.md b/docs/journals/260720-1612-stock-dividend-command-naming.md new file mode 100644 index 0000000..5b4065c --- /dev/null +++ b/docs/journals/260720-1612-stock-dividend-command-naming.md @@ -0,0 +1,34 @@ +# Stock Dividend Command Naming Journal + +## Context + +Researched official VSDC dividend notices and brainstormed clearer stock command +contracts for cash, share, and mixed dividend events. + +## What Happened + +- Approved `/stock_cash_dividend ` for cash dividends. +- Approved `/stock_share_dividend ` for share dividends. +- Approved `/stock_dividend ` for mixed + cash-and-share dividends. +- Official examples support ratios such as `4:1` and `100:10`. +- No implementation changes made yet. + +## Decisions + +- Share ratios use `owned:new`; both parts must be positive whole numbers. +- New shares use integer floor division. Fractional entitlements discarded. +- Cash input means VND paid per pre-event share. +- Mixed dividends calculate cash and new shares from the same pre-event holding, + then persist both results atomically. +- Stats history needs a one-time, idempotent migration: existing + `/stock_dividend` usage becomes `/stock_cash_dividend`, and `/stock_bonus` + usage becomes `/stock_share_dividend`. The new mixed `/stock_dividend` starts + with fresh stats. + +## Next Steps + +- Create an implementation plan covering handlers, registration, user-facing + text, command menu, atomic storage behavior, stats migrations, and tests. +- Implement the approved zero-result behavior: reject share-only payouts, but + still credit cash and report zero new shares for mixed payouts. diff --git a/docs/journals/260720-1621-stock-dividend-command-plan.md b/docs/journals/260720-1621-stock-dividend-command-plan.md new file mode 100644 index 0000000..9b8bf67 --- /dev/null +++ b/docs/journals/260720-1621-stock-dividend-command-plan.md @@ -0,0 +1,33 @@ +# Stock Dividend Command Plan Journal + +## Context + +Created the implementation plan at +`plans/260720-1616-stock-dividend-commands/` from the approved research and +brainstorm decisions. + +## What Happened + +- Added three pending phases: implement ratio dividend commands, migrate command + statistics, and verify user-facing contracts. +- Planned `/stock_cash_dividend` with VND/share, `/stock_share_dividend` with an + `owned:new` ratio, and combined `/stock_dividend` using both inputs. +- Preserved floor rounding, pre-event holdings, and one atomic portfolio save. +- Defined zero-share behavior: reject share-only events; combined events still + credit cash. +- Tightened migration design after review: each stats row gets a `prepared` + checkpoint containing its exact final target count. Crash retries set that + count instead of incrementing again. +- Could not persist the active-plan selection because `CK_SESSION_ID` was unset. + +## Status + +- Plan: pending +- Progress: 0/3 phases complete +- Source implementation: not started + +## Next Steps + +- Execute the three planned phases, beginning with command contracts and tests. +- Keep the stats migration before module registration so the reused + `/stock_dividend` name cannot misattribute historical usage. diff --git a/docs/journals/260720-1636-stock-dividend-plan-validation.md b/docs/journals/260720-1636-stock-dividend-plan-validation.md new file mode 100644 index 0000000..26c453a --- /dev/null +++ b/docs/journals/260720-1636-stock-dividend-plan-validation.md @@ -0,0 +1,31 @@ +# Stock Dividend Plan Validation Journal + +## Context + +Validated `plans/260720-1616-stock-dividend-commands/plan.md` and its three +pending phase documents before implementation. + +## Validation Result + +- Standard verification: 30 checked, 30 verified, 0 failed, 0 unverified. +- Confirmed cash input is a positive whole VND amount per share. +- Confirmed any positive whole-number `owned:new` ratio is accepted and echoed + unreduced. +- Confirmed repeated manual calls are allowed. No event ledger or duplicate-call + restriction; caller owns correctness. +- Confirmed permanent global and per-row migration markers. Incomplete runs + resume from `prepared` checkpoints using their exact target counts. +- Propagated these decisions through all three phase documents. +- Whole-plan consistency sweep reconciled one stale float-acceptance reference + with integer-input wording and found zero contradictions. + +## Status + +- Plan validation: complete +- Plan implementation: not started +- Source changes: none + +## Next Steps + +- Execute the validated phases in order: command behavior, stats migration, then + user-contract and full-project verification. diff --git a/docs/journals/260720-1715-stock-dividend-commands.md b/docs/journals/260720-1715-stock-dividend-commands.md new file mode 100644 index 0000000..cd7656b --- /dev/null +++ b/docs/journals/260720-1715-stock-dividend-commands.md @@ -0,0 +1,67 @@ +# Stock Dividend Commands Journal + +## Context + +Implemented the validated stock-dividend plan: clearer manual commands, +ratio-based share entitlement, and historical stats preservation. + +## What Changed + +- Added `/stock_cash_dividend ` for positive whole-VND + cash credits. +- Added `/stock_share_dividend ` with unreduced ratio + preservation and floor-rounded whole shares. +- Changed `/stock_dividend ` to apply cash + and shares from the same pre-event holding with one portfolio save. +- Share-only zero entitlement rejects with the minimum holding; combined events + still credit cash when shares round to zero. +- Updated registration, command menu, usage text, README, and contract tests; + removed `/stock_bonus` from active commands. + +## Stats Migration + +- Migrates `stock_dividend -> stock_cash_dividend` and + `stock_bonus -> stock_share_dividend`, including anonymous and per-user rows. +- Merges existing target counts without loss. +- Permanent global and per-row markers retain migration history. +- Each row stores a `prepared` exact target count before mutation. Retries set + that count after any write-boundary failure, preventing double increments. + +## Review Findings + +- Guarded `strconv.ParseInt` range errors so its saturated return value cannot be + accepted as valid input. +- Added exact `int64` share formatting so quantities above `2^53` do not lose + digits through `float64` conversion. +- Enforced exact float-backed VND balance addition at the `2^53` boundary; + inexact sums reject without saving. +- Reviewer: 9/10, approve. Adversarial review: PASS; no disproven claims or + reachable regressions. + +## Decisions + +- Ratios remain positive integer `owned:new` values and are echoed as entered; + no normalization. +- Commands are manual adjustments. Repeated calls remain allowed, with no event + ledger or duplicate-call guard; caller owns correctness. +- Overflow, exactness, ticker, and syntax validation remain mandatory despite + the manual workflow. + +## Verification + +- Passed: `go test ./internal/modules/stock` +- Passed: `go test ./internal/modules/stats` +- Passed: `go test ./cmd/server` +- Passed: `go test ./...` +- Passed: `go vet ./...` +- Passed: `telegram-commands.json` PowerShell `ConvertFrom-Json` +- Passed: `git diff --check` (line-ending warnings only) +- Skipped: Mongo-backed tests because `MONGODB_TEST_URL` was unset. +- Skipped: `golangci-lint run` because the binary was not installed. + +## Next Considerations + +- Run Mongo-backed migration tests when a test database is available. +- Run the lint gate when `golangci-lint` is installed. +- After deployment, verify migrated counts and retained system markers before + considering migration-runtime cleanup; keep historical stats and markers. diff --git a/plans/260720-1616-stock-dividend-commands/phase-01-implement-ratio-dividend-commands.md b/plans/260720-1616-stock-dividend-commands/phase-01-implement-ratio-dividend-commands.md new file mode 100644 index 0000000..d9a43b3 --- /dev/null +++ b/plans/260720-1616-stock-dividend-commands/phase-01-implement-ratio-dividend-commands.md @@ -0,0 +1,109 @@ +--- +phase: 1 +title: Implement Ratio Dividend Commands +status: completed +effort: '' +priority: P1 +dependencies: [] +--- + +# Phase 1: Implement Ratio Dividend Commands + + + +## Context Links + +- [Approved brainstorm](../reports/260720-1612-stock-dividend-command-brainstorm.md) +- [VSDC ratio research](../reports/260720-1608-dividend-notice-ratio-research.md) + +## Overview + +Replace the two ambiguous adjustment handlers with cash-only, share-only, and +combined dividend contracts. Centralize validated ratio math so all handlers +use the same overflow-safe, floor-rounded calculation. + +## Requirements + +- Functional: register `stock_cash_dividend`, `stock_share_dividend`, and + `stock_dividend` with the approved argument order and clear usage examples. +- Functional: accept cash only as positive whole VND per share; reject signs, + decimals, zero, parse overflow, and non-finite representations. +- Functional: accept only positive whole-number `owned:new` parts; reject + missing/extra colons, decimals, signs, zero, parse overflow, and invalid ticker. +- Functional: accept equivalent unreduced ratios and preserve the user's exact + valid ratio text in the success reply; do not require or display reduction. +- Functional: compute `floor(held * new / owned)` without overflowing `int64`; + compute the minimum holding for a non-zero share result safely. +- Functional: share-only rejects a zero result and reports that minimum; + combined credits cash and reports zero shares. +- Non-functional: cash and shares use the same pre-event holding; mutate only + after all validation; call `SavePortfolio` exactly once per successful event. +- Non-functional: allow intentional repeated calls; add no event ID, notice + lookup, history ledger, or duplicate-event guard. + +## Architecture + +Add a small dividend calculation helper beside the stock handlers. Parse cash +and ratio parts into positive integers while retaining the validated ratio +string for replies. Calculate quotient and remainder before +multiplication (or use checked operations) to preserve floor semantics without +`held * new` overflow. Each handler loads once, snapshots `held`, calculates +all outputs, mutates the in-memory portfolio, then saves once. The combined +handler must not let newly issued shares participate in its cash calculation. +Treat every successful invocation as an intentional manual adjustment. + +## Related Code Files + +- Create: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stock\dividends.go` — ratio parsing and checked entitlement math. +- Create: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stock\dividends_test.go` — parser, floor, minimum, and overflow boundaries. +- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stock\handlers.go` — three handlers and user-facing replies. +- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stock\handlers_test.go` — contracts, failures, pre-event basis, and one-save behavior. +- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stock\stock.go` — registry names, descriptions, handlers. +- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stock\portfolio_test.go` — portfolio outcomes where helper coverage belongs. + +## Implementation Steps + +1. Write table tests first for whole-VND parsing, `4:1`, `100:10`, equivalent + unreduced ratios, malformed inputs, `int64` boundaries, exact division, + floor division, preserved ratio text, and safe minimum holdings. +2. Add handler tests for usage text, invalid cash/ratio/ticker, no holdings, + zero-result divergence, and persistence failures. +3. Add combined tests proving cash and shares derive from the same snapshot and + one store write commits both; use an instrumented test store to count saves. +4. Implement parsing and overflow-safe integer entitlement helpers. Require + positive whole VND/share and reject fractional or overflowing totals. +5. Replace `handleBonus`/old cash `handleDividend` routing with explicit cash, + share, and combined handlers. Replies include ratio, old holding, cash, new + shares, and final holding as applicable. +6. Update the command registry to eight stock commands and remove public + registration of `stock_bonus`. +7. Run `gofmt` and focused stock tests. + +## Success Criteria + +- [x] All three approved commands and examples are registered exactly. +- [x] Cash rejects fractional VND; valid unreduced ratios are accepted and echoed unchanged. +- [x] Share math floors without overflow for every accepted `int64` input. +- [x] Share-only zero entitlement makes no change; combined zero entitlement credits cash and reports zero shares. +- [x] Successful combined events use pre-event holdings and one `SavePortfolio`. +- [x] Repeating a valid command applies the adjustment again; no deduplication state exists. +- [x] `go test ./internal/modules/stock` passes. + +## Risk Assessment + +- Overflow or invalid numeric acceptance could silently over-credit portfolios. + Mitigate with integer parsing, quotient/remainder math, checked cash totals, + and boundary tests. +- Partial mutation could diverge balances. Validate first, mutate in memory, + persist once; verify the unchanged state on every rejected path. +- Manual repeated calls can double-credit an event by design. State caller + responsibility clearly; do not silently infer or suppress duplicates. + +## Security Considerations + +No new authorization surface. Continue sender checks and strict ticker/input +validation; do not echo unbounded raw input. + +## Next Steps + +Phase 2 migrates persisted stats before the renamed commands are deployed. diff --git a/plans/260720-1616-stock-dividend-commands/phase-02-migrate-command-statistics.md b/plans/260720-1616-stock-dividend-commands/phase-02-migrate-command-statistics.md new file mode 100644 index 0000000..b7405e8 --- /dev/null +++ b/plans/260720-1616-stock-dividend-commands/phase-02-migrate-command-statistics.md @@ -0,0 +1,113 @@ +--- +phase: 2 +title: Migrate Command Statistics +status: completed +effort: '' +priority: P1 +dependencies: + - 1 +--- + +# Phase 2: Migrate Command Statistics + + + +## Context Links + +- [Approved compatibility decision](../reports/260720-1612-stock-dividend-command-brainstorm.md#compatibility-and-touchpoints) +- [Project stats compatibility rules](../../AGENTS.md#stats-compatibility) + +## Overview + +Move historical usage to the commands that retain the old meanings, then leave +`stock_dividend` empty for new combined-event usage. Guard the one-time move in +the shared `system` collection and run it during server startup. + +## Requirements + +- Functional: migrate `stock_dividend -> stock_cash_dividend` and + `stock_bonus -> stock_share_dividend`. +- Functional: include command-total rows (`uid=0`) and every per-user row; + merge source counts into existing target rows rather than overwrite them. +- Functional: preserve user ID/username metadata, remove migrated source rows, + and write the completion marker only after both mappings finish. +- Functional: a completed marker makes subsequent startups a no-op; tests cover + target merging and repeated invocation for memory and MongoDB stores. +- Functional: an incomplete migration resumes both remaining source rows and + `prepared` row checkpoints, including a checkpoint whose source was deleted. +- Functional: retain global and per-row markers permanently as migration history. +- Non-functional: migration errors fail startup before module registration, so + new command meanings never run against unmigrated history. + +## Architecture + +Extend stats startup maintenance to receive both `stats` and `system` +collections. Use the existing typed stats documents and `systemstate.Store`. +A stable marker such as `migration:stock-dividend-command-stats-v1` records +global completion. For each source row, first persist a stable per-row +`prepared` checkpoint in `system` containing the exact final target count. +Retries set the target to that checkpointed count instead of adding again, +then delete the source and mark the row complete. Preserve the best available +username deterministically. Mark global completion only after every row is +complete. On startup without a global completion marker, enumerate both source +rows and prepared checkpoints so deletion-before-row-complete can recover. +Retain all markers as migration history; add no cleanup path. + +## Related Code Files + +- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stats\startup.go` — startup signature, marker guard, and migration orchestration. +- Create: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stats\startup_test.go` — memory migration, merges, anonymous/users, failures, idempotency. +- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stats\startup_mongo_test.go` — Mongo indexes plus migration parity and rerun checks. +- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stats\stats_test.go` — visible stats attribution after migration if needed. +- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\cmd\server\main.go` — pass `stats` and shared `system` collections before module build. +- Reference: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\systemstate\systemstate.go` — existing marker store; no schema change expected. + +## Implementation Steps + +1. Add memory tests seeding source-only, target-only, merged anonymous, merged + per-user, multiple users, and both command mappings. +2. Assert exact post-migration keys/counts/metadata, source removal, marker + content, and no change after a second startup call. +3. Add injected-failure tests at prepare, target-write, source-delete, and + row-complete boundaries. Retry each case—including source already deleted— + and prove counts never duplicate. +4. Implement deterministic global and per-row marker keys. A prepared marker + stores the exact merged target count; retries overwrite with that value, + never increment an already migrated target again. +5. Implement the mapping helper using deterministic `usageKey` values and the + existing typed store. Keep mapping data declarative and local to stats. +6. Extend `InitStore` to create indexes and run the guarded migration; wrap + errors with migration/command context. +7. Wire `provider.Collection(systemstate.CollectionName)` from server startup + and preserve fail-fast logging before command registration. +8. Mirror critical merge, partial-retry, and idempotency cases in Mongo tests. +9. Run `gofmt`, focused stats tests, and focused server tests. + +## Success Criteria + +- [x] Old cash usage appears only under `stock_cash_dividend`. +- [x] Old bonus usage appears only under `stock_share_dividend`. +- [x] Existing target counts merge for anonymous and per-user rows without loss. +- [x] Retries after every partial-write boundary cannot duplicate counts. +- [x] Prepared checkpoints resume even when their source rows no longer exist. +- [x] Completed migration is idempotent and marker-backed in memory and MongoDB. +- [x] Global and per-row migration markers remain after completion. +- [x] Startup aborts on migration failure and `go test ./internal/modules/stats ./cmd/server` passes. + +## Risk Assessment + +- Reusing `stock_dividend` before migration would mislabel history. Keep the + migration before module construction and treat errors as fatal. +- A mid-migration process crash may leave partial work. Prepare each row's exact + final count before touching the target; retry by setting that value, not by + adding again. Scan prepared checkpoints independently from source rows. Do + not mark row/global completion early or delete historical markers. + +## Security Considerations + +The migration touches counts and public usernames only. Never log full records; +log marker/mapping names and aggregate counts. + +## Next Steps + +Phase 3 aligns all user-facing surfaces and runs the repository-wide gate. diff --git a/plans/260720-1616-stock-dividend-commands/phase-03-verify-user-contracts.md b/plans/260720-1616-stock-dividend-commands/phase-03-verify-user-contracts.md new file mode 100644 index 0000000..25df217 --- /dev/null +++ b/plans/260720-1616-stock-dividend-commands/phase-03-verify-user-contracts.md @@ -0,0 +1,95 @@ +--- +phase: 3 +title: Verify User Contracts +status: completed +effort: '' +priority: P1 +dependencies: + - 1 + - 2 +--- + +# Phase 3: Verify User Contracts + + + +## Context Links + +- [Approved command contracts](../reports/260720-1612-stock-dividend-command-brainstorm.md#approved-behavior) +- [Repository development rules](../../AGENTS.md#command-changes) + +## Overview + +Synchronize Telegram command-menu metadata and README documentation with the +implemented contracts, then run focused and repository-wide verification. + +## Requirements + +- Functional: command names, descriptions, argument order, examples, errors, + and menu behavior agree across registry, handlers, JSON, tests, and README. +- Functional: remove `stock_bonus` from active user surfaces; document that + cash is VND/share and share input is the `owned:new` notice ratio. +- Functional: document positive whole-VND input, acceptance of unreduced ratios, + preserved ratio text, and deliberate repeat-call behavior. +- Non-functional: preserve plan-approved migration behavior and run every gate + required for command, storage, migration, and shared startup changes. + +## Architecture + +Treat registered Go commands as runtime truth and `telegram-commands.json` as +the manual BotFather/menu source. Tests assert the final eight-command stock +registry and registered menu contents. README gives concise user-visible syntax +and the floor/pre-event behavior needed to use notices correctly. It states +that commands are manual adjustments: the caller verifies the notice and avoids +accidental duplicates; the bot enforces syntax and storage safety only. + +## Related Code Files + +- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\telegram-commands.json` — replace old dividend/bonus entries with three approved commands. +- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\README.md` — document syntax, VND/share, ratio direction, floor rounding, and combined behavior. +- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stock\handlers_test.go` — final registry and user-facing text assertions. +- Modify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\cmd\server\command_menu_test.go` — command-menu presence and removal checks. +- Verify: `C:\Users\miti99\Workspaces\tiennm99\miti99bot\internal\modules\stats\startup_test.go` — migration contracts remain green. + +## Implementation Steps + +1. Replace `stock_bonus` and old cash-only `stock_dividend` menu descriptions + with `stock_cash_dividend`, `stock_share_dividend`, and combined + `stock_dividend`; keep Telegram ordering coherent. +2. Add a compact README stock-command section with all three syntaxes and one + mixed example. State positive whole VND/share, `owned:new`, accepted + unreduced ratios, preserved ratio replies, integer floor rounding, + pre-event basis, and caller responsibility for repeat events. +3. Update registry/menu tests to assert new commands and reject old public + `stock_bonus`; ensure descriptions stay within Telegram limits. +4. Run `gofmt` over every changed Go file. +5. Run focused packages: `go test ./internal/modules/stock`, + `go test ./internal/modules/stats`, and `go test ./cmd/server`. +6. Run `go test ./...` and `go vet ./...`. +7. If `golangci-lint` is available, run `golangci-lint run`; record a skipped + gate explicitly when the binary is absent. + +## Success Criteria + +- [x] Registry, handlers, JSON, README, and menu tests expose the same three contracts. +- [x] No active user-facing surface advertises `stock_bonus` or old cash-only `/stock_dividend` syntax. +- [x] README distinguishes manual correctness responsibility from mandatory parser/overflow safety. +- [x] Focused tests, `go test ./...`, and `go vet ./...` pass. +- [x] `golangci-lint run` passes when available. +- [x] No unresolved questions remain. + +## Risk Assessment + +- Stale menu/docs can cause financially wrong manual entries. Search all active + surfaces for old names and syntax, while leaving migration fixtures intact. +- Mongo integration tests may require local infrastructure. Keep unit coverage + mandatory and report environment-based integration skips accurately. + +## Security Considerations + +Documentation must use synthetic holdings and tickers only; no tokens, +production data, or private portfolio records. + +## Next Steps + +Implementation can proceed phase-by-phase after plan approval. diff --git a/plans/260720-1616-stock-dividend-commands/plan.md b/plans/260720-1616-stock-dividend-commands/plan.md new file mode 100644 index 0000000..be1126e --- /dev/null +++ b/plans/260720-1616-stock-dividend-commands/plan.md @@ -0,0 +1,133 @@ +--- +title: Stock Dividend Commands +description: >- + Replace ambiguous stock adjustments with cash-only, share-only, and combined + dividend commands while preserving historical stats. +status: completed +priority: P1 +branch: main +tags: + - feature + - backend + - database +blockedBy: [] +blocks: [] +created: '2026-07-20T09:16:13.694Z' +createdBy: 'ck:plan' +source: skill +--- + +# Stock Dividend Commands + +## Overview + +Add three explicit public contracts: cash dividend in VND/share, share dividend +in `owned:new` ratio form, and a combined command. Use integer floor rounding, +pre-event holdings, and one portfolio save. Rename historical stats through an +idempotent startup migration before `/stock_dividend` gains its new meaning. + +## Phases + +| Phase | Name | Status | +|-------|------|--------| +| 1 | [Implement Ratio Dividend Commands](./phase-01-implement-ratio-dividend-commands.md) | Completed | +| 2 | [Migrate Command Statistics](./phase-02-migrate-command-statistics.md) | Completed | +| 3 | [Verify User Contracts](./phase-03-verify-user-contracts.md) | Completed | + +## Dependencies + +- Approved design: [Stock Dividend Command Brainstorm](../reports/260720-1612-stock-dividend-command-brainstorm.md) +- Evidence: [Dividend Notice Ratio Research](../reports/260720-1608-dividend-notice-ratio-research.md) +- Existing Go module, typed storage, `system` marker store, and stats startup path + +## Contracts + +- `/stock_cash_dividend ` credits a positive whole-VND amount per share. +- `/stock_share_dividend ` accepts any positive integer ratio, preserves its entered form in replies, and adds floor-rounded shares; zero result rejects. +- `/stock_dividend ` applies both from pre-event holdings; zero shares still credits cash. +- Stats move `stock_dividend -> stock_cash_dividend` and `stock_bonus -> stock_share_dividend`; combined usage starts fresh. +- Commands are deliberate manual adjustments: repeated calls are allowed and + no issuer-event deduplication or ratio reduction is imposed. + +## Validation Gate + +- Focused stock, stats migration, startup, and command-menu tests. +- `gofmt` changed Go files; run `go test ./...` and `go vet ./...`. +- Run `golangci-lint run` when installed. + +## Completion Criteria + +- Commands, replies, menu JSON, and README agree on names and argument order. +- Ratio math cannot overflow and always floors; combined state persists once. +- Stats migration merges anonymous/per-user targets and repeated startup is a no-op. +- No unresolved questions. + +## Validation Log + +### Session 1 — 2026-07-20 + +**Trigger:** User requested `/ck:plan validate` before implementation. +**Questions asked:** 4 + +### Verification Results + +- **Tier:** Standard (Fact Checker + Contract Verifier) +- **Claims checked:** 30 +- **Verified:** 30 | **Failed:** 0 | **Unverified:** 0 +- Verified current handlers/registry/portfolio storage, stats startup caller and + persisted row shape, shared system marker store, command-menu flow, tests, + README/JSON surfaces, and repository verification commands. +- Planned create-paths correctly do not exist yet: `dividends.go`, + `dividends_test.go`, and stats `startup_test.go`. +- **Failures:** None. + +#### Questions & Answers + +1. **[Assumption]** Should cash dividends require a positive whole VND amount per share and reject decimals? + - Options: Require positive whole VND (Recommended) | Preserve positive finite decimal input + - **Answer:** Require positive whole VND. + - **Rationale:** Matches approved manual-entry contract and avoids fractional VND credits. +2. **[Architecture]** Should share ratios accept any positive integer form and preserve the entered form without requiring reduction? + - Options: Accept and preserve any positive ratio (Recommended) | Require reduced ratios | Normalize replies + - **Answer:** Accept and preserve any positive ratio. + - **Rationale:** Mirrors issuer notices while avoiding unnecessary restrictions. +3. **[Scope]** Should repeated dividend commands be allowed without an issuer-event deduplication ledger? + - Options: Allow repeated manual adjustments (Recommended) | Add event IDs and deduplication + - **Answer:** Allow repeated manual adjustments; caller owns correctness. + - **Custom input:** "this is for user do manually, so not restrict anything, user call it will have reponsibility to make sure call it correct" + - **Rationale:** The feature is a manual portfolio adjustment, not an issuer-event ledger. +4. **[Risk]** Should migration retries scan prepared row checkpoints and retain all migration markers permanently? + - Options: Retry prepared rows and retain markers (Recommended) | Use global marker only | Clean row markers after completion + - **Answer:** Retry prepared rows and retain markers permanently. + - **Rationale:** Makes partial retries auditable and prevents double-counting. + +#### Confirmed Decisions + +- Manual responsibility does not bypass storage-safety validation: cash remains + positive whole VND; ratio parts remain positive integers; ticker and overflow + checks remain mandatory. +- No ratio canonicalization, notice lookup, event ID, or duplicate-call guard. +- Migration completion requires reconciling both source rows and prepared row + checkpoints; global and row markers remain as history. + +#### Action Items + +- [x] Propagate manual-input and repeat-call rules to Phase 1. +- [x] Propagate checkpoint retry/retention rules to Phase 2. +- [x] Propagate user-facing responsibility wording to Phase 3. + +#### Impact on Phases + +- Phase 1: tighten cash parsing; preserve ratio text; explicitly allow repeats. +- Phase 2: scan prepared checkpoints on retry; retain all markers. +- Phase 3: document manual responsibility and safety-validation boundary. + +### Whole-Plan Consistency Sweep + +- Files reread: `plan.md` and all three `phase-*.md` files. +- Decision deltas checked: 4. +- Reconciled stale references: 1 (`float acceptance` replaced by integer-input wording). +- Verified command names, input order, whole-VND rule, unreduced-ratio policy, + repeat-call policy, zero-share behavior, migration retry flow, and marker + retention agree across overview, requirements, steps, risks, and criteria. +- Unresolved contradictions: 0. diff --git a/plans/260720-1616-stock-dividend-commands/reports/pm-260720-1715-stock-dividend-completion.md b/plans/260720-1616-stock-dividend-commands/reports/pm-260720-1715-stock-dividend-completion.md new file mode 100644 index 0000000..631b9c6 --- /dev/null +++ b/plans/260720-1616-stock-dividend-commands/reports/pm-260720-1715-stock-dividend-completion.md @@ -0,0 +1,43 @@ +--- +type: pm-completion-report +plan: stock-dividend-commands +status: completed +created_at: 2026-07-20T17:15:00+07:00 +--- + +# Plan Complete: Stock Dividend Commands + +## Summary + +| Metric | Result | +|---|---| +| Plan status | Completed | +| Phase progress | 3/3 (100%) | +| Success criteria | 21/21 checked | +| Unresolved mappings | 0 | +| Review | 9/10, approved | +| Adversarial review | Passed | + +## Delivered + +- Explicit cash-only, share-only, and combined dividend contracts. +- Overflow-safe floor entitlement math using pre-event holdings and one save. +- Idempotent stats renames with retained migration checkpoints. +- Registry, Telegram menu, tests, and README aligned with public contracts. + +## Verification + +- Focused stock, stats, server, and command-menu tests: passed. +- `go test ./...`: passed. +- `go vet ./...`: passed. +- Whole-plan consistency sweep: passed; no unresolved mappings. + +## Known Limitation + +- MongoDB partial-boundary execution unavailable in the verification + environment. Deterministic retry behavior remains covered by available + focused verification; no completion blocker recorded. + +## Unresolved Questions + +None. diff --git a/plans/reports/260720-1608-dividend-notice-ratio-research.md b/plans/reports/260720-1608-dividend-notice-ratio-research.md new file mode 100644 index 0000000..0bf1d09 --- /dev/null +++ b/plans/reports/260720-1608-dividend-notice-ratio-research.md @@ -0,0 +1,112 @@ +--- +type: research-report +topic: dividend-notice-ratio-examples +conducted_at: 2026-07-20T16:08:21+07:00 +status: complete +--- + +# Research Report: Dividend Notice Ratio Examples + +## Summary + +Official VSDC notices support ratio-based share-dividend input. Notices express +the ratio as existing shares or rights to new shares, such as `4:1` and +`100:10`. Resulting fractional shares are rounded down and discarded. + +Keep cash input as VND per existing share. Although notices also state a cash +percentage, that percentage is based on par value; accepting the actual VND per +share avoids needing par-value data. + +## Methodology + +- Sources: 2 recent official VSDC notices +- Notice dates: 2026-05-05 and 2026-06-26 +- Terms: `trả cổ tức bằng tiền`, `trả cổ tức bằng cổ phiếu`, `tỷ lệ thực hiện` +- Scope: input semantics and rounding for the three proposed Telegram commands + +## Findings + +### IDC mixed dividend + +- Cash: 15% per share, explicitly `1 share receives 1,500 VND`. +- Shares: `100:10`, meaning 100 existing rights receive 10 new shares. +- Example: 139 existing shares produce `139 / 100 * 10 = 13.9`, rounded down + to 13 new shares; the 0.9 fraction is discarded. + +Source: [IDC: cash and stock dividends for 2025](https://www.vsd.vn/vi/ad/197421) + +### PTB mixed dividend + +- Cash: 5% per share, explicitly `1 share receives 500 VND`. +- Shares: `4:1`, meaning 4 existing shares receive 1 new share. +- Example: 2,026 existing shares produce `2,026 / 4 * 1 = 506.5`, rounded down + to 506 new shares; the 0.5 fraction is discarded. + +Source: [PTB: cash and stock dividends for 2025](https://vsd.vn/vi/ad/195203) + +## Comparative Analysis + +| Input | Benefit | Problem | +|---|---|---| +| Absolute new-share quantity | Matches current `/stock_bonus` behavior | User must calculate entitlement manually | +| Percentage | Familiar for rates such as 10% | Ambiguous parsing and less faithful to notice wording | +| `owned:new` ratio | Mirrors VSDC notices; easy to audit | Requires integer parsing and floor rounding | + +Recommendation: accept the `owned:new` ratio. + +## Command Recommendation + +```text +/stock_cash_dividend +/stock_share_dividend +/stock_dividend +``` + +Examples: + +```text +/stock_cash_dividend 1500 IDC +/stock_share_dividend 100:10 IDC +/stock_dividend 1500 100:10 IDC + +/stock_cash_dividend 500 PTB +/stock_share_dividend 4:1 PTB +/stock_dividend 500 4:1 PTB +``` + +Calculation: + +```text +new_shares = floor(existing_shares * new / owned) +cash_vnd = existing_shares * vnd_per_share +``` + +For a mixed dividend, calculate both values from the same pre-event holding, +then save cash and shares atomically. + +## Validation and Pitfalls + +- Require positive whole-number ratio parts; reject `0:1`, `4:0`, negatives, + decimals, missing colon, and extra colons. +- Reduce equivalent ratios internally if useful, but preserve the entered ratio + in the reply for auditability. +- Use integer arithmetic and floor division for shares; avoid floating-point + rounding. +- Reject events producing zero new shares, or explicitly confirm zero payout; + recommended behavior: reject with the minimum holding required. +- Calculate cash before adding new shares so newly distributed shares do not + receive cash from the same event. +- Keep cash input in VND/share. Do not derive it from a percentage unless the + portfolio also stores the security's par value. + +## Next Steps + +1. Confirm zero-share-result behavior. +2. Finalize the three command contracts and reply wording. +3. Plan command renames, combined atomic update, stats migrations, and tests. + +## Unresolved Questions + +- When a valid ratio yields zero new shares, should the command reject the + event or record a zero-share result while still applying cash in the combined + command? diff --git a/plans/reports/260720-1612-stock-dividend-command-brainstorm.md b/plans/reports/260720-1612-stock-dividend-command-brainstorm.md new file mode 100644 index 0000000..9b3b16a --- /dev/null +++ b/plans/reports/260720-1612-stock-dividend-command-brainstorm.md @@ -0,0 +1,165 @@ +--- +type: brainstorm-report +topic: stock-dividend-command-naming +created_at: 2026-07-20T16:12:33+07:00 +status: approved +modes: + html: false + wiki: false +--- + +# Brainstorm Report: Stock Dividend Commands + +## Summary + +Approved three explicit commands: + +```text +/stock_cash_dividend +/stock_share_dividend +/stock_dividend +``` + +Cash uses VND per existing share. Share distributions use the official-notice +ratio form, such as `4:1` or `100:10`. Mixed dividends calculate both outcomes +from the pre-event holding and persist them atomically. + +## Problem-First Analysis + +### Solution-Jumping Diagnosis + +The original names encoded outcomes inconsistently: `/stock_bonus` added shares +while `/stock_dividend` added cash. Users could not distinguish a true bonus +share event from a stock dividend or represent a mixed dividend notice. + +### Underlying Problem + +Users need command names and inputs that map directly to Vietnamese dividend +notices, while preserving simple manual paper-portfolio accounting. + +### Assumptions and Validation + +| Assumption | Risk if wrong | Validation | +|---|---|---| +| Notices provide share ratios | Users must calculate quantities | Confirmed by VSDC `4:1` and `100:10` notices | +| Cash per share is sufficient | Percentage input may be expected | Notices explicitly state VND received per share | +| Fractions round down | Portfolio could over-credit shares | Confirmed by both VSDC examples | +| Mixed event uses one record-date holding | Cash may include newly issued shares | Calculate both before mutation | + +### Problem Statement + +Paper-trading users cannot accurately record cash-only, share-only, and mixed +dividends because current command semantics are incomplete and `stock_bonus` +does not necessarily mean a stock dividend. Success means each notice maps to +one obvious command and produces auditable integer-share and cash results. + +### Alternative Framings + +1. Keep current commands: smallest change, but mixed events need two commands + and terminology remains ambiguous. +2. Use explicit cash/share commands plus a combined command: clear, direct, + matches actual notices. Selected. +3. Add a generic corporate-action command with subtypes: extensible but too + complex for three small manual operations. + +### Evidence Status + +Strong for syntax and rounding: two recent official VSDC mixed-dividend notices +use cash-per-share explanations, `owned:new` ratios, and floor rounding. + +### Validation Plan + +- Unit-test ratio parsing, overflow boundaries, and floor division. +- Table-test cash-only, share-only, and combined handlers. +- Verify combined calculation uses the same pre-event holding. +- Test one-time stats migrations for anonymous and per-user rows. +- Reject the design if real notices require unsupported fractional settlement + rather than discarded fractions. + +### Stakeholder Message + +Use explicit cash and share commands for single-form notices, and the combined +command for mixed notices. Inputs mirror VSDC wording, reducing manual math and +making bot replies easy to compare against source notices. + +## Evaluated Approaches + +| Approach | Pros | Cons | Decision | +|---|---|---|---| +| Absolute new-share quantity | Reuses current handler behavior | Manual calculation; hides rounding | Reject | +| Percentage share input | Familiar shorthand | Ambiguous; less faithful to notices | Reject | +| `owned:new` share ratio | Mirrors notices; deterministic | Needs parser and integer safety | Approve | + +## Approved Behavior + +### Cash-only + +```text +/stock_cash_dividend 1500 IDC +cash = existing_shares * 1500 VND +``` + +### Share-only + +```text +/stock_share_dividend 100:10 IDC +new_shares = floor(existing_shares * 10 / 100) +``` + +Reject a share-only event when the valid ratio produces zero new shares and +report the minimum holding needed. + +### Mixed + +```text +/stock_dividend 1500 100:10 IDC +``` + +Calculate cash and new shares from the same pre-event holding. When the share +result is zero, still credit cash and report zero new shares. Save once so cash +and shares cannot diverge after a partial failure. + +## Compatibility and Touchpoints + +- Rename `/stock_bonus` to `/stock_share_dividend`. +- Move historical `/stock_bonus` stats to `/stock_share_dividend`. +- Move historical cash-only `/stock_dividend` stats to + `/stock_cash_dividend` before reusing `/stock_dividend` for mixed events. +- Update `internal/modules/stock/stock.go`, handlers, handler tests, and + `telegram-commands.json`. +- Add idempotent startup migration through the shared `system` collection; + cover anonymous rows, per-user rows, target-row merging, and repeated startup. + +## Risks + +- Integer multiplication can overflow before division; validate bounds or use a + safe quotient/remainder calculation. +- Reusing `/stock_dividend` without migrating old stats mislabels historical + cash-only usage as combined usage. +- Applying shares before cash calculation overpays the same event. +- Accepting decimal or zero ratio components creates undefined rounding. + +## Success Criteria + +- `4:1` and `100:10` parse; malformed or non-positive ratios fail clearly. +- Share calculations use integer floor semantics. +- Cash/share/mixed commands match their documented examples. +- Mixed updates are atomic and use pre-event holdings. +- Existing command statistics remain preserved under the correct new meanings. +- Focused tests, `go test ./...`, and `go vet ./...` pass. + +## Sources + +- [VSDC IDC mixed dividend notice](https://www.vsd.vn/vi/ad/197421) +- [VSDC PTB mixed dividend notice](https://vsd.vn/vi/ad/195203) +- [Supporting research report](./260720-1608-dividend-notice-ratio-research.md) + +## Next Steps + +Create a tests-first implementation plan because this change renames public +commands, changes persisted stats attribution, and modifies financial +calculation behavior. + +## Unresolved Questions + +None.