Repository navigation
Lite opens time follow-ups and signs generic webhooks (#6012) - #6064
Merged
Merged
Conversation
added 8 commits
October 18, 2026 02:01
Owner
Author
|
Claude posting for Erik Darling. The signing-key screens work against a real Windows Credential Manager. This is the click test the PR body left open ("A person should click Generate, Rotate and Remove once"). PowerShell UI Automation drove a Lite built from Settings page (Delivery tab):
Route dialog (Manage Notification Routes, Add Route, then Edit):
Not checked: the route row's signing flag in the DuckDB file, because Lite holds the file open while it runs. The behaviour above depends on that flag, and it acted correctly across restarts. |
4 of 5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #6012.
Why
Lite had no time follow-ups and sent generic webhooks unsigned. Darling got both in #6008 (time follow-ups) and #5998 (Standard Webhooks signing). This is the Lite side (decision D19 A: time only, no deploy condition. D1 to D4 signing).
What changes
Part A, time follow-ups (done).
follow_up_ofandfollow_up_atoninvestigation_requests, in the sharedAddedColumnslist so a start heals a missing column.submit_investigation_reporttakesfollow_up_at, checked by the sharedValidateFollowUp(past, or over 30 days, is refused asinvalid). The description says Lite opens a time follow-up only.FollowUpLimit(same words as Darling) and nothing is stored.SweepAsyncopens each due follow-up once, in its own transaction after the resolved step: a new request withfollow_up_of, a copy of the source's alert rows, and onefollow_upjournal row (triggertime, once per earlier request). It returns the count inFollowUps.follow_up_ofthat names a request it deletes (as Lite: recurrence, server names and report fields (#5632 Phase C L2) #6011 does forfamily_of).list_investigation_requests. I added no delivery.InvestigationToolContractnow listsfollow_up_atas shared. Onlyfollow_up_conditionstays Darling-only. The Lite golden tool-text SHA moved for the same reason.Part B, signed generic webhooks (done).
AppAlertSettings.GenericWebhookSigningandGenericSigningKeyUnreadablefor the settings-row URL, andNotificationRoute.GenericSigningfor a route's own URL.RouteSecretAccess:GenericWebhookSigningKeyand...Previousfor the settings row,NotificationRouteSigning_<id>_Current/Previousfor a route. Only a has-key flag and the previous key's end time are plain:generic_signing_setandgeneric_signing_untilin settings.json, andgeneric_signing_setandgeneric_signing_untilcolumns onnotification_routes(v75). The key is never in settings.json, DuckDB, a log, a label, a tooltip, the clipboard or an error message.OffChannelswith no fallback to the app-wide URL. A signed destination is never sent unsigned.WebhookSigning.RotationOverlap).SendTestGenericAsynctakes an optionalsigningargument. Darling's callers pass nothing.Test plan
grep -lclasses for every changed file (merged dev at1f431e207):Total: 419, Failed: 0.*SignedWebhook* *InvestigationToolContract* *McpToolGuide* *ViewerSigningKey* *WebhookAlertService* *DarlingMcpInvestigationTools* *InvestigationReportFields* *AlertDeliveryChannel* *GenericWebhook* *WebhookPostTimeout*:Total: 344, Failed: 0, Skipped: 5.GenericSigningKeyUnreadablewas not reverted separately. The old code has no key flag, so it cannot pass there.ba9a38333(merged with dev):*LiteSignedWebhookTests* *NotificationRouteStoreTests* *SettingsSampleTests* *SettingsValueReadTests*plus every class thatgrep -lnames for the changed files (AppAlertSettings,SettingsSaveReport,SettingsFileGuard,SettingsWindowTabsLite,AlertSettingsControlWiring,AlertSettingsCredentialLoad,NotificationRouteUi,NotificationRouteParity,NotificationRouteRealCredential,NotificationRouteSecretGeneration,NotificationRouteSecretSave,NotificationRoutesMcp,NotificationRouteTruthfulness,GenericWebhook,McpSettingsGuard,ClickThroughUiText):Total: 241, Failed: 0. The seat's full run at87f3b4caefound one failure, my own Remove pin (fixed, above). Darling.Tests was 34076 with 0 failed at1f431e207. Round 2 changed no Darling file and no shared model, so I did not rerun it.verify-full.sh(seat) atba9a38333: Darling.TestsTotal: 34076, Errors: 0, Failed: 0, Skipped: 3914, Not Run: 1, Lite.TestsTotal: 10703, Errors: 0, Failed: 0, Skipped: 5. Both builds had 0 warnings and 0 errors.Pins:
LiteInvestigationFollowUpTests(sweep opens once at its time and the brief carries the earlier report, the 21st is refused and nothing is stored, 31 days is refused through the tool, the purge clears a danglingfollow_up_of, v75 on a new and an old file) andLiteSignedWebhookTests(the three headers over the exact bytes and a receiver check with one changed byte, both keys verify in a rotation, the key is in the secret store and in no table or text, an unreadable key sends nothing and does not fall back, and no key means unsigned as before). Existing pins I updated: the v74 schema asserts to v75, the route column count 19 to 21, the sample settings.json list, the golden tool-text SHA.Round 2 (seat review): what changes
LiteWebhookSigningKeys.Generatehas an overload that takessaveState. It writes the key, calls the save, and if the save fails or throws it puts Credential Manager back (old current under the current name, old previous under the previous name, or deletes a name that held nothing) and returns an error and no key. If the put-back fails too, the message says Credential Manager may hold a key that was never shown. Both callers use it.App.TryWriteSettingreturns whether settings.json was written (WriteSettingnow calls it and keeps its behaviour). Generate and Rotate move the in-memory flag with the file and put it back when the write fails. Remove: if the flag cannot be written the in-memory flag is left as the file has it, so the URL reads as unreadable and is held back, and the message says so (RemoveNotSavedText).GenerateSigningKeyAsyncruns the flag UPDATE through the same step. A failed UPDATE returns(null, error)and nothing throws into the click handler.RemoveSigningKeyAsyncsays the settings were not saved and the route is held back when its UPDATE fails.DuckDbNotificationRouteStore.SigningStateAsync(routeId)returns the row's flag, rotation end and anUnreadablebit (flag set and the key will not open. Not set for a channel that is off because its URL secret will not open). The dialog reads it on load and after Generate or Remove, so an unreadable key shows the unreadable text, offers Rotate key and enables Remove, and the rotation end shown is the saved one, not now plus 7 days.NotificationRouteis unchanged, so no Darling class is affected. The dialog click handlers also catch an exception from the store delegate, so a click never ends the app.TryWriteSigningFlag(route) andPersistGenericSigning(settings), both from the screens' Generate and Remove clicks. The readers areHydrate,SigningStateAsyncandLoadAlertSettings.Round 2: pins
New in
LiteSignedWebhookTests(8): first key with a failing save (no key, error, Credential Manager empty). Rotation with a failing save (old current and previous restored). Rotation with no previous key and a throwing save (no previous name left behind). Failing save plus a refusing put-back (message says so, no key). A route whose flag UPDATE fails on Generate and on Remove. The store read reports unreadable for flag set and no key, and not unreadable for a channel off because its URL will not open. The store read reports no rotation end when the old key would not open, and the saved end otherwise. The route UPDATE is made to fail with a unique index ongeneric_signing_set(a second route going to the same value is a real DuckDB constraint error). The first run of the Remove pin failed because new routes have a NULL flag and a unique index lets NULLs repeat. The test now sets the other routes to false first. That was a test bug found by the seat's full run, not a product bug.Revert proof (temporary branch, deleted): with the save step ignored, the Remove flag check off and
Unreadableforced false, 7 of the 8 new pins fail (the "not unreadable for a URL-off channel" pin and the rotation-end pin hold on the old code too. They guard the cases the fix must not break). The screen change in the route dialog has no pin: a test cannot click the dialog.CHANGELOG entry
SECTION: Added
ENTRY: - Lite opens time follow-ups and signs generic webhooks ([#6012]) - An agent report can ask for a follow-up request at a time up to 30 days ahead, and Lite opens it with the earlier report in its brief. Lite's generic webhook can carry a per-destination signing key (Standard Webhooks headers), kept in Windows Credential Manager and shown once.
REF: [#6012]: #6012
Deferred and follow-ups