Commit Graph

1706 Commits

Author SHA1 Message Date
Joseph Doherty 4307c38117 docs(code-review): record SMS-feature review findings + reconcile NotificationService doc
Per-module code review of the SMS notifications feature (reviewed at d6ead8ae) following
code-reviews/REVIEW-PROCESS.md. 19 findings across 7 modules — 1 High, 5 Medium, 13 Low:

- ManagementService-024 (High): provider-config updates Admin-gated to match the UI.
- ConfigurationDatabase-025, CentralUI-034/035, ManagementService-025 (Medium): migration
  data-safety guard, type-aware recipient badge, FromNumber-optional (Messaging-Service-only),
  empty-token-clear guard.
- Remaining Low: secret-encryption + diff + dispatch + factory + contract tests, truncation,
  ctor guard, reserved retry-field docs.
- Won't Fix: Transport-015/016 (shared repo-wide import patterns, not SMS-specific),
  Commons-026 (breaking ergonomics-only change). Deferred: ConfigurationDatabase-027 (live-SQL
  migration test).

All findings closed (0 pending). README.md regenerated; Component-NotificationService.md
updated for the FromNumber-optional + reserved-retry-fields outcomes.
2026-06-19 15:30:32 -04:00
Joseph Doherty 33e1802e6d feat(sms): make FromNumber optional — support Twilio Messaging-Service-only configs (UI-Med-2)
Code-review finding UI-Med-2: the design doc + delivery adapter treat FromNumber and
MessagingServiceSid as either-or, but the entity ctor, EF schema, UI and CLI all hard-
required FromNumber — so a Messaging-Service-only Twilio config (a normal production
setup) could not be created. Bring the implementation into line with the spec:

- Commons: SmsConfiguration.FromNumber -> string? (ctor fromNumber optional);
  UpdateSmsConfigCommand.FromNumber -> string?.
- ConfigurationDatabase: FromNumber.IsRequired(false) + migration SmsFromNumberOptional
  (ALTER COLUMN nullable, idempotent; Down backfills '' — harmless, MsgSid keeps it
  deliverable) + regenerated model snapshot.
- Transport: SmsConfigDto.FromNumber -> string? (round-trips a Messaging-Service-only config).
- CentralUI: form validation requires AccountSid + at-least-one-of(FromNumber, MsgSid);
  nullable create/edit paths; From-number help text.
- CLI: --from-number no longer Required; BuildUpdateSmsConfigCommand validates the either-or.
- Adapter: From branch null-forgiving (guarded by the existing incomplete-config check).

Tests: ManagementActor MsgSid-only persists null FromNumber; CLI MsgSid-only builds +
neither-throws + contract (--from-number not Required); CentralUI MsgSid-only save.
2026-06-19 15:19:40 -04:00
Joseph Doherty a9393c8913 test(sms): regression tests for code-review fixes
Lock the behaviors changed by the review-fix commit + the security invariants:

- ManagementActorTests: UpdateSms/SmtpConfig now require Administrator (updated the
  existing success cases from Designer); + UpdateSmsConfig_WithDesignerRole_Returns
  Unauthorized and _WithEmptyAuthToken_PreservesExistingToken regression tests.
- SecretEncryptionTests: SmsConfiguration.AuthToken stored-encrypted round-trip +
  null round-trip (AccountSid stays plaintext) — guards ApplySecretColumnEncryption.
- ArtifactDiffTests: CompareSmsConfiguration New/Identical/Modified + the secret
  presence-only invariant (value never echoed, presence-flip shows <present> only).
- UpdateCommandContractTests: notification sms update core fields Required, --auth-token optional.
- NotificationListsPageTests: SMS recipient badge shows phone, not "Name <>".
- NotificationOutboxActorDispatchTests: SMS-typed notification routes to the SMS
  adapter (StubAdapter.Type made configurable), not the Email adapter.
- NotificationRecipientTests (new): ForEmail/ForSms + public-ctor invariants.
2026-06-19 15:09:47 -04:00
Joseph Doherty cd8e4872f6 fix(sms): code-review fixes — Admin-gate provider-config updates, guard secret-clear/data-loss, type-aware UI
Findings from the per-module code review of the SMS feature (code-reviews/):

- ManagementService (High): UpdateSmsConfig + UpdateSmtpConfig were Designer-gated
  while both /notifications/{sms,smtp} pages enforce RequireAdmin — a Designer
  blocked in the UI could still rotate a production credential via CLI. Moved both
  to the Administrator arm so the actor gate matches the UI.
- ManagementService (Medium): UpdateSmsConfig treated --auth-token "" as a value,
  silently clearing the stored Twilio token. Guard on IsNullOrWhiteSpace so empty ==
  omitted (SMTP Credentials keeps its null-only guard — empty is valid for no-auth).
- CentralUI (Medium): NotificationLists recipient badge rendered Name <Email>
  unconditionally, showing "Name <>" for SMS lists. Now type-aware (phone for SMS).
- ConfigurationDatabase (Medium): AddSmsNotifications.Down() backfilled NULL emails
  to '' — silent data loss for SMS-only recipients. Added a pre-drop guard that
  refuses rollback while such rows exist.
- NotificationOutbox (Low): SMS body truncation could split a surrogate pair at the
  cap boundary; back off one code unit to stay well-formed.
- Commons (Low): NotificationRecipient public ctor name-guard now matches the
  ForEmail factory (IsNullOrWhiteSpace). Documented SmsConfiguration.MaxRetries/
  RetryDelay as RESERVED (dispatcher reuses the shared SMTP-derived retry policy).
2026-06-19 15:02:02 -04:00
Joseph Doherty d6ead8ae62 test(sms): live Playwright E2E for SMS config page + Type selector + Type column (S11) 2026-06-19 11:54:25 -04:00
Joseph Doherty f8e2d7a934 test(sms): de-race NotificationOutbox load-flake under parallel-suite CPU oversubscription (S11) 2026-06-19 11:40:55 -04:00
Joseph Doherty c5c8511379 docs(sms): reconcile Component/CLAUDE/README docs for SMS notifications (S11) 2026-06-19 11:21:56 -04:00
Joseph Doherty 9558d98343 feat(sms): export wizard SMS-config selection (S10c) 2026-06-19 11:16:32 -04:00
Joseph Doherty c3501ecd72 feat(sms): complete SmsConfig bundle export/import wiring + GetSmsConfigurationByIdAsync (S10b) 2026-06-19 11:10:39 -04:00
Joseph Doherty 78fadb82d2 test/fix(sms): scope S8 Type-column assertion + S9 RetryDelay display (review polish) 2026-06-19 11:00:06 -04:00
Joseph Doherty 4860aeff62 feat(sms): SMS configuration Central UI page + nav (S9) 2026-06-19 10:52:56 -04:00
Joseph Doherty 538117d114 fix(sms): reject notification-list Type change on update (S6 review follow-up) 2026-06-19 10:51:58 -04:00
Joseph Doherty 58ac06a358 feat(sms): NotificationLists Type column (S8) 2026-06-19 10:51:11 -04:00
Joseph Doherty 73df322a66 feat(sms): CLI list --type/--phones + notification sms group + channel-aware recipients (S6) 2026-06-19 10:40:09 -04:00
Joseph Doherty cdfd0ffbd2 feat(sms): Transport recipient PhoneNumber + SmsConfig round-trip (S10) 2026-06-19 10:39:12 -04:00
Joseph Doherty f0c69aad83 feat(sms): NotificationListForm adapter-gated Type selector + per-type recipients (S7) 2026-06-19 10:38:29 -04:00
Joseph Doherty 4555a3f333 test(sms): harden S3 adapter secret-redaction + coverage (review follow-up) 2026-06-19 10:28:58 -04:00
Joseph Doherty a1d484a5ff feat(sms): Twilio SmsNotificationDeliveryAdapter + classifier + options + DI (S3) 2026-06-19 10:14:52 -04:00
Joseph Doherty 609bdb37ef feat(sms): list Type + SMS-config management commands/handlers (S5) 2026-06-19 10:12:55 -04:00
Joseph Doherty bffbb0c2da feat(sms): stamp Notification.Type from list at ingest (S4) 2026-06-19 10:11:05 -04:00
Joseph Doherty 3827b98484 fix(sms): S2 review — add DeleteSmsConfigurationAsync + schema/repo tests + doc nit 2026-06-19 10:06:44 -04:00
Joseph Doherty b46691747c feat(notifications): central SMS config + nullable recipient contact (S2)
Implement the central ConfigurationDatabase side of SMS notifications:

- NotificationConfiguration: EmailAddress now nullable (SMS-only recipients
  carry a PhoneNumber, no email); add PhoneNumber nvarchar(32); add
  SmsConfigurationConfiguration (AuthToken sized as the encrypted column,
  mirroring SmtpConfiguration.Credentials; timeout/retry mapped REQUIRED for
  ctor-default round-trip fidelity).
- ScadaBridgeDbContext: add SmsConfigurations DbSet, encrypt AuthToken at rest
  via EncryptedStringConverter, and cover SmsConfiguration in the schema-only
  secret-write guard.
- NotificationRepository: implement the four INotificationRepository SMS-config
  methods (resolves the 4x CS0535), mirroring the SMTP methods' stage-only /
  separate-SaveChangesAsync discipline.
- Migration AddSmsNotifications: idempotent (guarded) ALTER EmailAddress nullable,
  ADD PhoneNumber, CREATE SmsConfigurations; Down reverses cleanly (backfills
  NULL emails before restoring NOT NULL).
2026-06-19 09:57:55 -04:00
Joseph Doherty 095361b73f fix(sms): repair S1 build breaks — null-filter EmailAddress projections + SiteNotificationRepository SMS stubs
S1 made NotificationRecipient.EmailAddress nullable + added SmsConfiguration
and four INotificationRepository SMS methods, breaking compilation beyond the
intentionally-deferred central NotificationRepository.

Fix 1 (CS8620/CS8604 nullable EmailAddress projections, email-only paths):
- NotificationOutbox EmailNotificationDeliveryAdapter: filter non-null emails
- DeploymentManager ArtifactDeploymentService: filter non-null emails
- Transport EntitySerializer: filter non-null emails into NotificationRecipientDto

Fix 2 (CS0535): stub the four SMS-config methods on SiteRuntime
SiteNotificationRepository (central-only — NotSupportedException, matching the
existing 'Managed via artifact deployment from Central' write-path pattern).

Doc nits: reword NotificationRecipient private ctor and SmsConfiguration.AuthToken
comments.

The central ConfigurationDatabase.NotificationRepository compile break is left
as-is (S2 implements those four methods).
2026-06-19 09:51:07 -04:00
Joseph Doherty c5378f8723 feat(sms): NotificationType.Sms + recipient phone + SmsConfiguration + repo iface (S1) 2026-06-19 09:42:27 -04:00
Joseph Doherty 07dae35533 docs(sms): implementation plan + task persistence for SMS notifications (S1-S11) 2026-06-19 09:38:32 -04:00
Joseph Doherty 28cab6e881 docs(sms): design for SMS (Twilio) notification adapter + T10 plumbing (T9 Teams→SMS pivot) 2026-06-19 09:34:45 -04:00
Joseph Doherty c72d7b7902 test(dcl): de-race MxGateway Unsubscribe stops-routing under load (#288) 2026-06-19 05:01:42 -04:00
Joseph Doherty 84cb8c2abb test(comm): de-race StreamRelay null-map + SiteConnectionUp gauge under load (#288) 2026-06-19 05:00:13 -04:00
Joseph Doherty ab40534986 build(deps): targeted NuGetAuditSuppress for SQLitePCLRaw advisory GHSA-2m69-gcr7-jv3q (#292)
The high-sev advisory is on transitive native SQLitePCLRaw.lib.e_sqlite3 2.1.11
(via Microsoft.Data.Sqlite/EFCore.Sqlite). Microsoft.Data.Sqlite.Core 10.0.9 (latest
10.0.x) still references SQLitePCLRaw.core 2.1.11, so a supported-line bump doesn't clear
it; the only patched lib is the SQLitePCLRaw 3.x line, which is an unsupported/risky forced
override under a Data.Sqlite built for 2.1.x. Suppress ONLY this advisory (auditing stays on
for everything else) so the full solution AND the docker in-container restore build cleanly
without the blanket /p:NuGetAudit=false. No version/code change; runtime byte-identical.
Revisit when MS ships a 10.0.x referencing a patched bundle.
2026-06-19 04:52:42 -04:00
Joseph Doherty f08a4d609f test(playwright): M9 surface coverage — template search, schema-library, move-connection (#263) 2026-06-19 04:31:38 -04:00
Joseph Doherty c68b9cccd2 test(playwright): harden #230 line-diff test — force-open nested details, robust +/- hunk assert
Live-run fixes: nested <details> made the summary selector ambiguous (strict-mode);
force every disclosure open via JS. Drop the brittle specific-line-text ('99') assert
(depends on CLI-seeded bodies) for a ≥1 add-hunk + ≥1 remove-hunk count check. (#230)
2026-06-19 04:27:20 -04:00
Joseph Doherty 529921f2de test(playwright): M8 import Map step + per-line diff coverage (#230) 2026-06-19 04:23:12 -04:00
Joseph Doherty 7bd081ba50 fix(ui): move Razor comment outside <select> start tag in Health.razor (circuit crash, #291)
Pre-existing render-crash (introduced by eb4bce3e): a @* *@ comment between
attributes inside the site-health-trends <select> start tag rendered as an
invalid attribute name in a real browser (InvalidCharacterError on setAttribute),
tearing down the SignalR circuit whenever Site Health Trends renders. bUnit's
virtual DOM masked it; the live Playwright smoke (KpiTiles) caught it. Comment
moved outside the start tag.
2026-06-19 04:16:01 -04:00
Joseph Doherty 4b8986036e test(dcl): deterministic stale-generation reseed-drop after second failover (DCL-027 #233) 2026-06-19 03:39:19 -04:00
Joseph Doherty e814bf5efb fix(ui): per-field sub-schema for escape-hatch Monaco editors + dispose guard (#261) 2026-06-19 03:28:58 -04:00
Joseph Doherty 282bc8b53c fix(ui): templates root-menu Escape + double-menu guard + reorder disabled tests (#258)
- Root context menu now has tabindex/focus + Escape-key close (OnRootMenuKeyDown) mirroring the node menu
- Opening root menu calls _tree.DismissNodeContextMenu(); opening node menu fires OnNodeContextMenuOpened → DismissRootContextMenu so only one menu is ever visible
- Add FolderContextMenu_MoveUp_IsDisabled_OnFirstSibling and FolderContextMenu_MoveDown_IsDisabled_OnLastSibling bUnit tests
2026-06-19 03:28:18 -04:00
Joseph Doherty 8f85cce298 fix(ui): schema-library delete-audit name + busy guard + edit-row guard + sanitized create-race test (#260) 2026-06-19 03:28:11 -04:00
Joseph Doherty e3b83f8561 fix(cli): guard min-time overflow + normalize 0 exec-timeout to null + stale comment (#54 review) 2026-06-19 03:24:11 -04:00
Joseph Doherty 597d664a53 fix(cli): warn on --trigger-kind without --trigger-config (#257)
InjectAnalysisKind returns null on a null/empty trigger-config, so passing
--trigger-kind strict WITHOUT --trigger-config silently dropped the kind on
template script add/update and template alarm update. The CLI now detects that
combination (TriggerKindWillBeIgnored) and prints a clear warning to stderr,
then continues (warn-and-continue: the kind is advisory metadata, not a required
field, so the entity is still created — just without the requested analysis kind).
The --trigger-kind help text on all three commands now documents that it requires
--trigger-config, as does the CLI README.

This commit also carries the shared CLI command-builder file (Commands/TemplateCommands.cs)
and README, which the same builders edit for both #257 and the #54 flag additions —
the #54 message contracts/handler/UI/tests landed in the preceding commit.

- TriggerKindWillBeIgnored predicate + WarnIfTriggerKindIgnored stderr warning,
  wired into script add/update and alarm update SetActions.
- Shared option descriptions document the --trigger-config requirement.
- Adds the #54 CLI flags (--min-time-between-runs, --execution-timeout-seconds) and
  TryParseMinTimeBetweenRuns to the same builder file.
- Tests: TemplateTriggerKindIgnoredTests pins the warn predicate.
2026-06-19 03:14:24 -04:00
Joseph Doherty ae25b5a8d6 feat(cli): add --execution-timeout-seconds + --min-time-between-runs to template script (#54)
Expose TemplateScript.ExecutionTimeoutSeconds and MinTimeBetweenRuns (previously
settable only via Transport bundle import) on the CLI and Central UI authoring surfaces.

- Commons: add additive trailing optionals MinTimeBetweenRuns (TimeSpan?) and
  ExecutionTimeoutSeconds (int?) to Add/UpdateTemplateScriptCommand.
- ManagementActor: thread both new fields into the built TemplateScript on add/update.
- CLI template script add/update: new --min-time-between-runs (duration: ms/s/min,
  bare number = seconds, 0 = unset, mirroring DurationInput) and
  --execution-timeout-seconds (int) flags, with client-side duration validation.
- Central UI TemplateEdit: add an Execution timeout input (seconds) on the script
  trigger tab, mirroring the existing Min-time-between-runs control; null/0 = site default.
- Tests: TemplateScriptTimingTests pins the option surface + duration parsing; updated
  the stale 'no UI control' comment on the TemplateService round-trip test.
2026-06-19 03:14:10 -04:00
Joseph Doherty 5185486a3c fix(deploy): include ElementDataType in RevisionHashService.HashableAttribute (#290)
Fold a List attribute's ElementDataType into the hashable projection so a
change to the element type (e.g. Int32 -> Double) with identical JSON-encoded
values is detected as a staleness/revision change. Inserted in alphabetical
position; null ElementDataType (scalars) is omitted by the canonical
serializer (WhenWritingNull), so scalar-only configs hash identically to
before. DiffService.AttributesEqual gains the same comparison to keep the
structured diff in parity with the staleness hash. Adds tests for differing
vs. equal ElementDataType (hash + diff) and the scalar no-op guard.
2026-06-19 03:07:06 -04:00
Joseph Doherty 79fe098886 test(transport): regression — empty-ConnectionName binding skipped on import (#229) 2026-06-19 03:06:10 -04:00
Joseph Doherty 69e0d546b5 fix(audit): route SecuredWrite audit via ICentralAuditWriter for SourceNode stamping (#206) 2026-06-19 02:37:48 -04:00
Joseph Doherty fb18253f32 fix(deploy): normalize snapshot List values (Decode→Encode) before staleness/diff (#102); CLI --value native-List help 2026-06-19 02:24:26 -04:00
Joseph Doherty 454e47ea38 fix(mgmt): pass sharedScripts to design-time template $ref validation (#259) 2026-06-19 02:21:15 -04:00
Joseph Doherty d844405cec fix(templates): hoist (DataType,ElementDataType,Value) attribute validation into TemplateService (#92) 2026-06-19 02:19:35 -04:00
Joseph Doherty e51104af5f refactor(kpi): shared public KpiMetrics catalog — source consts + UI pages key off one symbol (#178) 2026-06-19 02:04:10 -04:00
Joseph Doherty 47f5ca687c refactor(commons): consolidate List element-type/coercion into AttributeValueCodec; InstanceActor + CLI reuse it (#93) 2026-06-19 02:03:09 -04:00
Joseph Doherty 2935c41bf7 fix(inbound-api): recursively coerce nested Object/List values to typed CLR (#55) 2026-06-19 02:02:10 -04:00
Joseph Doherty 2843781db4 docs(#70): correct SqliteInsertSql comment — test drives EF SQL generator, not the constant 2026-06-19 01:57:02 -04:00