From 07dae35533a20c0a02f10572b4c60e721a1a2d5a Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 19 Jun 2026 09:38:32 -0400 Subject: [PATCH] docs(sms): implementation plan + task persistence for SMS notifications (S1-S11) --- docs/plans/2026-06-19-sms-notifications.md | 280 ++++++++++++++++++ ...2026-06-19-sms-notifications.md.tasks.json | 17 ++ 2 files changed, 297 insertions(+) create mode 100644 docs/plans/2026-06-19-sms-notifications.md create mode 100644 docs/plans/2026-06-19-sms-notifications.md.tasks.json diff --git a/docs/plans/2026-06-19-sms-notifications.md b/docs/plans/2026-06-19-sms-notifications.md new file mode 100644 index 00000000..b81baac0 --- /dev/null +++ b/docs/plans/2026-06-19-sms-notifications.md @@ -0,0 +1,280 @@ +# SMS Notifications (T9 pivot + T10) Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:subagent-driven-development to implement this plan task-by-task. + +**Goal:** Add `NotificationType.Sms` + a Twilio-REST `SmsNotificationDeliveryAdapter` behind the existing `INotificationDeliveryAdapter` seam, plus the T10 enum/UI/CLI Type-selector plumbing, so notification lists can deliver to ZB team members by SMS. Email is unchanged. + +**Architecture:** Mirror the Email/SMTP stack. A list's existing `NotificationList.Type` decides the channel; `Notify.To` stays type-agnostic; central stamps the Notification's Type from the list at ingest; the outbox actor already routes `Dictionary` and parks on a missing adapter. Twilio called directly over REST (no SDK). Secrets encrypted via the existing Data-Protection `EncryptedStringConverter`. + +**Tech Stack:** C#/.NET 10, Akka.NET, EF Core (MS SQL), Blazor Server, System.CommandLine, `IHttpClientFactory`, ASP.NET Data Protection. **No new NuGet packages** (`Microsoft.Extensions.Http` is already centrally managed). + +**Design doc:** `docs/plans/2026-06-19-sms-notifications-design.md` (committed 28cab6e8). + +**Execution rules:** dedicated worktree `feat/sms-notifications` off `main` `c72d7b79`. Implementers do NOT create worktrees. Pathspec commits only (`git commit -m "msg" -- `, `-m` before `--`, never `git add -A`; `git add ` for new files; retry on index.lock). ≤2-3 concurrent committers per wave + post-wave HEAD-presence check (`git merge-base --is-ancestor HEAD`). Targeted builds/tests per task; full-solution build + docker rebuild + Playwright + live smoke only at INT (S11). TreatWarningsAsErrors=true. + +--- + +## Wave / dependency map + +- **Wave 1:** S1 (Commons foundation) — blocks all. +- **Wave 2:** S2 (Config-DB migration) ∥ S5 (Management commands+handlers). Both ⟵ S1. +- **Wave 3:** S3 (adapter) ⟵ S1,S2 ∥ S4 (ingest stamping) ⟵ S1 ∥ S6 (CLI) ⟵ S5. +- **Wave 4:** S7 (list form) ∥ S8 (list page Type column) ∥ S9 (SMS config page) ∥ S10 (Transport). All ⟵ S1/S5 (S9,S10 also ⟵ S2). Cap at 3 concurrent; queue the 4th. +- **Wave 5:** S11 (INT) ⟵ all. + +--- + +### Task S1: Commons foundation — NotificationType.Sms, recipient phone, SmsConfiguration entity, repo interface + +**Classification:** standard +**Estimated implement time:** ~5 min +**Parallelizable with:** none (foundational) + +**Files:** +- Modify: `src/ZB.MOM.WW.ScadaBridge.Commons/Types/Enums/NotificationType.cs` +- Modify: `src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Notifications/NotificationRecipient.cs` +- Create: `src/ZB.MOM.WW.ScadaBridge.Commons/Entities/Notifications/SmsConfiguration.cs` +- Modify: `src/ZB.MOM.WW.ScadaBridge.Commons/Interfaces/Repositories/INotificationRepository.cs` +- Test: `tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/` (add a recipient-factory + SmsConfiguration shape test if a suitable test class exists; else a small new test file) + +**Steps:** +1. Add `Sms` to `NotificationType` (after `Email`). Update the enum's XML doc to "Email and SMS supported." NOTE: `Commons.Tests` may have an enum-count assertion — if so, update it (this is the known enum-count-drift pattern). +2. `NotificationRecipient`: add `public string? PhoneNumber { get; set; }`; relax `EmailAddress` to `string?`. Keep the existing `NotificationRecipient(string name, string emailAddress)` ctor (email path). Add static factories `ForEmail(string name, string email)` and `ForSms(string name, string phoneNumber)` (set the appropriate field, leave the other null; validate name non-empty). Keep a parameterless-friendly path for EF. +3. Create `SmsConfiguration` POCO (persistence-ignorant): `int Id`, `string AccountSid`, `string? AuthToken`, `string FromNumber`, `string? MessagingServiceSid`, `string? ApiBaseUrl`, `int ConnectionTimeoutSeconds`, `int MaxRetries`, `TimeSpan RetryDelay`. Mirror `SmtpConfiguration` constructor/validation style. +4. `INotificationRepository`: add `Task GetSmsConfigurationAsync(CancellationToken=default)`, `Task> GetAllSmsConfigurationsAsync(CancellationToken=default)`, `Task AddSmsConfigurationAsync(SmsConfiguration, CancellationToken=default)`, `Task UpdateSmsConfigurationAsync(SmsConfiguration, CancellationToken=default)` (match the SMTP method shapes already present). +5. Build `dotnet build src/ZB.MOM.WW.ScadaBridge.Commons/...`; run any touched Commons tests. +6. Commit (pathspec; `git add` the new SmsConfiguration.cs). + +**Acceptance:** Commons builds 0/0; `NotificationType.Sms` exists; recipient carries nullable phone + factories; `SmsConfiguration` + repo methods defined. + +--- + +### Task S2: Configuration Database — EF mappings, encryption, idempotent migration + +**Classification:** high-risk +**Estimated implement time:** ~5 min +**Parallelizable with:** S5 + +**Files:** +- Modify: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Configurations/NotificationConfiguration.cs` +- Modify: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/ScadaBridgeDbContext.cs` +- Modify: `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/NotificationRepository.cs` (or wherever `INotificationRepository` is implemented — locate it) +- Create: new EF migration under `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Migrations/` +- Test: `tests/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Tests/` (mapping/round-trip + encryption test) + +**Steps:** +1. `NotificationRecipientConfiguration`: `EmailAddress` → `.IsRequired(false)`; add `PhoneNumber` `.HasMaxLength(32)` (nullable). +2. Add `SmsConfigurationConfiguration : IEntityTypeConfiguration` (key, `AccountSid` required maxlen, `FromNumber` required, `AuthToken`/`MessagingServiceSid`/`ApiBaseUrl` nullable, sensible maxlens). Add `DbSet` + apply config. +3. `ScadaBridgeDbContext.ApplySecretColumnEncryption`: add `modelBuilder.Entity().Property(s => s.AuthToken).HasConversion(converter);`. +4. Implement the four `INotificationRepository` SMS-config methods (mirror SMTP impls exactly). +5. Generate the migration (`dotnet ef migrations add AddSmsNotifications ...`). Then make it **idempotent** per repo convention (guard column add / table create / column-alter the way prior migrations in this repo do — match an existing idempotent migration as reference). Migration must: add `NotificationRecipients.PhoneNumber` (nullable), alter `NotificationRecipients.EmailAddress` to nullable, create `SmsConfigurations` table. +6. Verify NO `PendingModelChangesWarning`: `dotnet build` + a model-drift check (the repo has a pattern/test for this — run it). +7. Build Config-DB + run touched Config-DB tests. Commit (pathspec). + +**Acceptance:** Config-DB builds; migration idempotent + no model drift; encryption applied to `AuthToken`; recipient phone + nullable email mapped; round-trip test green. + +--- + +### Task S3: SmsNotificationDeliveryAdapter (Twilio REST) + classifier + options + DI + adapter tests + +**Classification:** high-risk +**Estimated implement time:** ~5 min +**Parallelizable with:** S4, S6 + +**Files:** +- Create: `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/Delivery/SmsNotificationDeliveryAdapter.cs` +- Create: `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/Delivery/SmsErrorClassifier.cs` +- Create: `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/SmsOptions.cs` +- Modify: `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/ServiceCollectionExtensions.cs` +- Modify: `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/ZB.MOM.WW.ScadaBridge.NotificationOutbox.csproj` +- Modify (test): `tests/ZB.MOM.WW.ScadaBridge.NotificationOutbox.Tests/ServiceRegistrationTests.cs` +- Create (test): `tests/ZB.MOM.WW.ScadaBridge.NotificationOutbox.Tests/Delivery/SmsNotificationDeliveryAdapterTests.cs` + +**Steps:** +1. `SmsErrorClassifier`: static `IsTransient(HttpStatusCode)` (5xx/408/429) + `IsTransient(Exception, CancellationToken)` (HttpRequestException/Timeout/TaskCanceled when not caller-cancelled). Mirror `SmtpErrorClassifier`'s enum+static-method shape. +2. `SmsOptions` (bind `ScadaBridge:Sms`): `int MaxMessageLength = 1600`, `int ConnectionTimeoutSeconds = 30` (config-row value overrides). Validate. +3. `SmsNotificationDeliveryAdapter : INotificationDeliveryAdapter`, `Type => NotificationType.Sms`. Ctor: `INotificationRepository`, `IHttpClientFactory`, `ILogger`, `IOptions`. `DeliverAsync`: + - Load list by `notification.ListName` → `Permanent` if missing. + - Load recipients; filter to non-empty `PhoneNumber` → `Permanent` if none. + - Load `GetSmsConfigurationAsync()` → `Permanent` if null/incomplete. + - Compose body = `Subject` + "\n" + `Body`, truncate to `MaxMessageLength`. + - For each phone: `POST {ApiBaseUrl}/2010-04-01/Accounts/{AccountSid}/Messages.json`, Basic auth header `AccountSid:AuthToken`, form `To`/`From`(or MessagingServiceSid)/`Body`. Classify per response. + - Roll up per D6 (all-accepted→Success; any-transient→Transient; mix accepted+permanent→Success + LastError note; all-permanent→Permanent). `ResolvedTargets` = accepted numbers. + - Scrub AuthToken from any logged error via `CredentialRedactor.Scrub`. +4. `ServiceCollectionExtensions.AddNotificationOutbox`: register `SmsNotificationDeliveryAdapter` as scoped `INotificationDeliveryAdapter` (alongside Email); `services.AddHttpClient("Twilio")`; bind `SmsOptions`. +5. `.csproj`: add `` (version comes from Directory.Packages.props — do NOT add a version attribute). +6. **Update `ServiceRegistrationTests`**: the `Assert.Single(adapters)` test must now assert both Email and SMS adapters are registered (e.g. `Assert.Equal(2, adapters.Count)` + both types present); keep/add a `SmsNotificationDeliveryAdapter.Type == Sms` assertion. +7. Adapter unit tests with a fake `HttpMessageHandler`: 201 success (single + multi recipient), 500/429/timeout → Transient, 400/401 → Permanent, mix accepted+400 → Success with LastError note, no-config/no-recipients/list-missing → Permanent, AuthToken never appears in returned error string. +8. Build NotificationOutbox + run NotificationOutbox.Tests. Commit (pathspec; `git add` new files). + +**Acceptance:** adapter builds; both adapters registered; all adapter unit tests + the updated ServiceRegistration test green; no AuthToken leakage. + +--- + +### Task S4: Ingest type-stamping in NotificationOutboxActor + +**Classification:** standard +**Estimated implement time:** ~3 min +**Parallelizable with:** S3, S6 + +**Files:** +- Modify: `src/ZB.MOM.WW.ScadaBridge.NotificationOutbox/NotificationOutboxActor.cs` (the ingest path that currently hard-codes `NotificationType.Email`, ~line 1147) +- Test: `tests/ZB.MOM.WW.ScadaBridge.NotificationOutbox.Tests/` (ingest type-stamping test) + +**Steps:** +1. At ingest, resolve the target list (`GetListByNameAsync(msg.ListName)`); stamp `Type = list?.Type ?? NotificationType.Email` onto the new `Notification`. Keep the at-least-once insert-if-not-exists semantics intact (do NOT change idempotency). Add a brief comment replacing the old "all notifications are email" comment. +2. Test: ingest for an Email list stamps Email; ingest for an Sms list stamps Sms; ingest for a missing list defaults Email (and later parks at delivery — assert default only). +3. Build + run NotificationOutbox.Tests (filtered). Commit (pathspec). + +**Acceptance:** notifications inherit the list's Type at ingest; idempotency unchanged; tests green. + +--- + +### Task S5: Management — list-command Type + SMS-config commands/handlers + +**Classification:** standard +**Estimated implement time:** ~5 min +**Parallelizable with:** S2 + +**Files:** +- Modify: `src/ZB.MOM.WW.ScadaBridge.Commons/Messages/Management/NotificationCommands.cs` +- Modify: `src/ZB.MOM.WW.ScadaBridge.ManagementService/ManagementActor.cs` +- Test: `tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/` (handler tests) + +**Steps:** +1. `NotificationCommands.cs`: add `NotificationType Type = NotificationType.Email` to `CreateNotificationListCommand` and `UpdateNotificationListCommand` (additive trailing param). Add `record ListSmsConfigsCommand;` and `record UpdateSmsConfigCommand(int SmsConfigId, string AccountSid, string FromNumber, string? MessagingServiceSid = null, string? ApiBaseUrl = null, string? AuthToken = null);` (mirror `UpdateSmtpConfigCommand`, preserve-if-null for AuthToken). +2. `ManagementActor`: in Create/Update list handlers, persist `cmd.Type` on the `NotificationList`. Add `HandleListSmsConfigs` + `HandleUpdateSmsConfig` (preserve-if-null AuthToken/ApiBaseUrl), role-gate (Designer/Admin per SMTP), and audit via a credential-free `SmsConfigPublicShape` (mirror `SmtpConfigPublicShape` — never log/return AuthToken). Wire the command routing entries. +3. Handler tests: create list with Type=Sms persists Sms; update SMS config preserves AuthToken when null, never surfaces AuthToken in audit/response. +4. Build Commons + ManagementService + run ManagementService.Tests (filtered). Commit (pathspec). + +**Acceptance:** list commands carry Type; SMS-config commands/handlers exist, role-gated, secret-safe; tests green. + +--- + +### Task S6: CLI — list --type/--phones + notification sms group + +**Classification:** standard +**Estimated implement time:** ~5 min +**Parallelizable with:** S3, S4 + +**Files:** +- Modify: `src/ZB.MOM.WW.ScadaBridge.CLI/Commands/NotificationCommands.cs` +- Modify: `src/ZB.MOM.WW.ScadaBridge.CLI/README.md` +- Test: `tests/ZB.MOM.WW.ScadaBridge.CLI.Tests/` + +**Steps:** +1. List `create`/`update`: add `--type email|sms` (default email) and `--phones` (comma-separated E.164). Validate: email type requires `--emails`; sms type requires `--phones`; reject mismatch. Build `Create/UpdateNotificationListCommand` with `Type` + the right recipient set. (Recipients for SMS map to phone numbers — coordinate with how recipients are submitted; if recipients are added via a separate command, set Type on the list and feed phones through the recipient path.) +2. Add `notification sms list` (→ `ListSmsConfigsCommand`) and `notification sms update --id --account-sid --from-number [--messaging-service-sid] [--api-base-url] [--auth-token]` (→ `UpdateSmsConfigCommand`), mirroring the `notification smtp` group. Render JSON/table; never print AuthToken. +3. Update CLI README (notification section: `--type`/`--phones`, `sms` group). +4. Build CLI + run CLI.Tests (filtered). Commit (pathspec). + +**Acceptance:** CLI creates SMS lists + manages SMS config; validation enforced; secret never printed; tests + README updated. + +--- + +### Task S7: Central UI — NotificationListForm Type selector + per-type recipient input + +**Classification:** standard +**Estimated implement time:** ~5 min +**Parallelizable with:** S8, S9, S10 + +**Files:** +- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Notifications/NotificationListForm.razor` +- Test: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/` (NotificationListForm tests) + +**Steps:** +1. Add an **adapter-gated** Type selector: source the options from the registered `INotificationDeliveryAdapter` Types (inject the adapter set or a small accessor; if injecting the adapters into the UI is awkward, expose a server-side accessor that returns the registered `NotificationType` set). Today renders Email + SMS. Selector enabled on create; **read-only on edit** (Id.HasValue) to prevent recipient-kind mismatch. +2. Recipient editor: when Type=Email show the Email input + validate email; when Type=Sms show a Phone input + validate E.164. Build the recipient via `NotificationRecipient.ForEmail/ForSms`. Update the recipients table to show Email or Phone per type. +3. On save, pass `Type` to the create command / new list. +4. bUnit tests: selector renders Email+SMS; choosing SMS shows phone input + validation; create persists Type; edit shows Type read-only. **Razor hygiene:** any `@* *@` comment must sit OUTSIDE element start tags (the recurring circuit-crash landmine). +5. Build CentralUI + run the NotificationListForm tests (filtered). Commit (pathspec). + +**Acceptance:** Type selector adapter-gated + create-only; per-type recipient input + validation; tests green; no Razor start-tag comments. + +--- + +### Task S8: Central UI — NotificationLists Type column + +**Classification:** small +**Estimated implement time:** ~3 min +**Parallelizable with:** S7, S9, S10 + +**Files:** +- Modify: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Notifications/NotificationLists.razor` +- Test: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/NotificationListsPageTests.cs` + +**Steps:** +1. Add a **Type** column to the lists table (render `NotificationType`). Keep markup tokenized/consistent with the M10 table conventions. +2. Update/extend `NotificationListsPageTests` to assert the Type column renders (e.g. an SMS list shows "Sms"). +3. Build CentralUI + run NotificationListsPageTests. Commit (pathspec). + +**Acceptance:** Type column renders; test green. + +--- + +### Task S9: Central UI — SMS configuration page + +**Classification:** standard +**Estimated implement time:** ~5 min +**Parallelizable with:** S7, S8, S10 + +**Files:** +- Create: `src/ZB.MOM.WW.ScadaBridge.CentralUI/Components/Pages/Notifications/SmsConfiguration.razor` +- Modify: the nav/menu component that lists `/notifications/smtp` (add `/notifications/sms`) +- Test: `tests/ZB.MOM.WW.ScadaBridge.CentralUI.Tests/Pages/` (SmsConfiguration page test) + +**Steps:** +1. New page `/notifications/sms` (Admin-only, mirror `SmtpConfiguration.razor`): list SMS configs, create/edit form (AccountSid, FromNumber, MessagingServiceSid, ApiBaseUrl, AuthToken — AuthToken write-only/masked, preserve-if-blank on edit), save via repository/management. Never render the stored AuthToken back. +2. Add the nav entry next to the SMTP settings entry. +3. bUnit: page renders config rows; AuthToken input is masked/not pre-filled; save calls the repository. Razor start-tag comment hygiene. +4. Build CentralUI + run the page test. Commit (pathspec; `git add` new file). + +**Acceptance:** SMS config page works + Admin-gated + secret-safe; nav entry added; test green. + +--- + +### Task S10: Transport — recipient PhoneNumber DTO + SmsConfigDto round-trip + +**Classification:** standard +**Estimated implement time:** ~5 min +**Parallelizable with:** S7, S8, S9 + +**Files:** +- Modify: `src/ZB.MOM.WW.ScadaBridge.Transport/Serialization/EntityDtos.cs` +- Modify: `src/ZB.MOM.WW.ScadaBridge.Transport/Serialization/EntitySerializer.cs` +- Test: `tests/ZB.MOM.WW.ScadaBridge.Transport.*Tests/` + +**Steps:** +1. `NotificationRecipientDto`: add `string? PhoneNumber` (trailing, nullable → backward-compatible). `SmsConfigDto` mirroring `SmtpConfigDto` (secret in `SecretsBlock`). Add `IReadOnlyList SmsConfigs` to `BundleContentDto` and `IReadOnlyList SmsConfigurations` to `EntityAggregate`. +2. `EntitySerializer`: map recipient PhoneNumber both directions; map SMS configs export/import (mirror SMTP, including SecretsBlock handling). Keep `schemaVersion` evolution additive per repo convention. +3. Tests: recipient phone round-trips; SMS config round-trips with secret in SecretsBlock; old bundle without PhoneNumber deserializes to null. +4. Build Transport + run Transport tests (filtered). Commit (pathspec). + +**Acceptance:** SMS recipient phone + SMS config travel in bundles; backward-compatible; tests green. + +--- + +### Task S11: Integration — build, migration drift, docker, Playwright, live smoke, docs, review + +**Classification:** high-risk +**Estimated implement time:** ~10 min (+ docker rebuild wall-time) +**Parallelizable with:** none + +**Files:** +- Modify: `docs/requirements/Component-NotificationService.md`, `docs/requirements/Component-NotificationOutbox.md` +- Modify: `CLAUDE.md` (NotificationType = Email+Sms; T9/T10 delivered as SMS — flip the M6 deferral note; add the SMS-adapter decision bullet) +- Modify: `README.md` (component table / notes if needed) +- Create (test): `tests/ZB.MOM.WW.ScadaBridge.CentralUI.PlaywrightTests/Notifications/SmsNotificationListTests.cs` (or extend existing notification Playwright) + +**Steps:** +1. Full-solution build: `dotnet build ZB.MOM.WW.ScadaBridge.slnx` — 0 warnings/errors. +2. EF model-drift check (no `PendingModelChangesWarning`). +3. Full unfiltered managed test run `dotnet test ZB.MOM.WW.ScadaBridge.slnx` (run 2–3× for load-flake surfacing per the integration memory). Triage any red: prove pre-existing via `git diff --stat c72d7b79..HEAD -- ` before attributing. +4. Docker rebuild: `bash docker/deploy.sh`; `/health/ready` 200 on the active node. +5. Live smoke via CLI against the cluster: set an SMS config (fake AccountSid + `--api-base-url` pointed at a mock, OR document Twilio sandbox), create an `--type sms` list with a phone recipient, submit a notification, verify it routes to the SMS adapter and parks-with-reason when the fake returns an error (proves routing + classification end-to-end). Also verify an Email list still delivers (no regression). +6. Playwright: SMS-list create flow (Type selector → SMS → phone recipient → save) against the freshly-rebuilt cluster; broad notification-page smoke. Razor circuit-crash check: load `/notifications/lists/create` + `/notifications/sms` in a real browser (the bUnit-invisible render-crash gate). +7. Docs reconciliation: Component docs + CLAUDE.md + README + CLI README synced; confirm the M6 deferral note is flipped to "delivered (SMS pivot)." +8. Whole-branch integration review over `git diff c72d7b79..HEAD` (Bash-capable reviewer): end-to-end trace of the new `NotificationType.Sms` path (submit → ingest stamp → adapter dispatch → Twilio POST → park/retry), shared-component injection regressions (any new ctor/`[Inject]` dep → grep every fixture across ALL test projects), and secret-leak audit (AuthToken never logged/returned/rendered). +9. Commit docs (pathspec). Then hand to finishing-a-development-branch. + +**Acceptance:** full build + full managed tests green; docker healthy; live smoke proves SMS routing + Email no-regression; Playwright + render-crash gate green; docs synced; whole-branch review INTEGRATION-CLEAN. diff --git a/docs/plans/2026-06-19-sms-notifications.md.tasks.json b/docs/plans/2026-06-19-sms-notifications.md.tasks.json new file mode 100644 index 00000000..4bc4c9aa --- /dev/null +++ b/docs/plans/2026-06-19-sms-notifications.md.tasks.json @@ -0,0 +1,17 @@ +{ + "planPath": "docs/plans/2026-06-19-sms-notifications.md", + "tasks": [ + {"id": 299, "subject": "S1: Commons foundation — NotificationType.Sms + recipient phone + SmsConfiguration + repo iface", "status": "pending"}, + {"id": 300, "subject": "S2: Config-DB — EF mappings + AuthToken encryption + idempotent migration", "status": "pending", "blockedBy": [299]}, + {"id": 303, "subject": "S5: Management — list-command Type + SMS-config commands/handlers", "status": "pending", "blockedBy": [299]}, + {"id": 301, "subject": "S3: SmsNotificationDeliveryAdapter (Twilio REST) + classifier + options + DI + tests", "status": "pending", "blockedBy": [299, 300]}, + {"id": 302, "subject": "S4: NotificationOutboxActor ingest type-stamping from list", "status": "pending", "blockedBy": [299]}, + {"id": 304, "subject": "S6: CLI — list --type/--phones + notification sms group", "status": "pending", "blockedBy": [303]}, + {"id": 305, "subject": "S7: Central UI — NotificationListForm adapter-gated Type selector + per-type recipient input", "status": "pending", "blockedBy": [299, 303]}, + {"id": 306, "subject": "S8: Central UI — NotificationLists Type column", "status": "pending", "blockedBy": [299]}, + {"id": 307, "subject": "S9: Central UI — SMS configuration page (/notifications/sms)", "status": "pending", "blockedBy": [300, 303]}, + {"id": 308, "subject": "S10: Transport — recipient PhoneNumber DTO + SmsConfigDto round-trip", "status": "pending", "blockedBy": [299]}, + {"id": 309, "subject": "S11: INT — build, drift, docker, Playwright, live smoke, docs, whole-branch review", "status": "pending", "blockedBy": [300, 301, 302, 303, 304, 305, 306, 307, 308]} + ], + "lastUpdated": "2026-06-19" +}