Merge candidate 20260907T173016Z-core-runtime

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-09-07 18:46:39 +01:00
5 changed files with 92 additions and 2 deletions
@@ -15,6 +15,8 @@
## Purpose
Delivery-log diagnostic text is a notification-attempt projection, not an agent admission or liveness verdict. Masking embedded destination credentials preserves failure context without changing agent identities, credentials, session replacement or removal policy. Consumers must use the retained failureClass and outcome as delivery evidence only; a transport error does not establish that the monitored agent is offline.
Optional container filesystem observations add no command or lifecycle
authority. The collector's existing disk-metrics option controls them. Native
reads require the exact local container process identity and current namespace
@@ -20,6 +20,8 @@
## Purpose
The delivery-log response redacts embedded webhook URLs in errorMessage rather than treating the entire diagnostic as a URL. Non-secret operation and failure context survive alongside notificationId, destinationId, outcome, failureClass and attempts; malformed URL diagnostics fail closed. The HTTP payload proof is TestContract_DeliveryDiagnosticPayload; notification handler tests pin plain, credential-bearing and malformed errors. No response keys or route permissions change.
Action reconciliation refreshes the durable product investigation record from
the authoritative session/action even when the finding outcome already matches.
The same builder owns initial completion and later refresh. Original prose,
@@ -21,6 +21,8 @@
## Purpose
Delivery-log URL masking occurs in the HTTP diagnostic projection and does not rewrite retained attempt history or destination configuration. Preserved failure context and attempt identifiers describe notification delivery only, not a restore result or storage freshness. Malformed URL diagnostics fail closed in the response; this does not delete the retained failure, change retention windows, or grant retry, storage or recovery authority.
Container filesystem evidence is a read-only observation at a named resource
mountpoint. It grants no storage mutation, recovery or host-capacity authority.
A full container tmpfs does not establish host-array exhaustion. Unknown byte
@@ -206,8 +206,7 @@ func TestGetDeliveryLogReturnsEntriesWithRedactedErrors(t *testing.T) {
response.Entries[0].AlertIDs[0] != "vm-offline-101" {
t.Fatalf("failed entry = %#v", response.Entries[0])
}
if strings.Contains(response.Entries[0].ErrorMessage, "supersecret") ||
!strings.Contains(response.Entries[0].ErrorMessage, "token=REDACTED") {
if response.Entries[0].ErrorMessage != "post https://hooks.example.test/notify?token=REDACTED returned 401" {
t.Fatalf("error message not redacted: %q", response.Entries[0].ErrorMessage)
}
if response.WindowDays != 30 ||
@@ -329,3 +328,38 @@ func containsAny(value string, needles ...string) bool {
}
return false
}
// These are response-boundary checks, not just URL-helper checks.
func TestGetDeliveryLogDiagnosticContext(t *testing.T) {
for _, tc := range []struct{ name, input, want string }{
{"plain", "connection refused", "connection refused"},
{"userinfo", "Post https://user:password@example.test/hook: timeout", "Post https://REDACTED@example.test/hook: timeout"},
{"malformed", "Post https://user:password@example.test/%zz: timeout", "[invalid webhook URL]"},
} {
t.Run(tc.name, func(t *testing.T) {
manager := new(MockNotificationManager)
monitor := new(MockNotificationMonitor)
monitor.On("GetNotificationManager").Return(manager).Once()
manager.On("GetDeliveryLog", mock.Anything, 0).Return([]notifications.DeliveryLogEntry{
{NotificationID: "attempt-1", ErrorMessage: tc.input, FailureClass: "transport"},
}, nil).Once()
rec := httptest.NewRecorder()
NewNotificationHandlers(nil, monitor).GetDeliveryLog(rec, httptest.NewRequest(http.MethodGet, "/api/notifications/delivery-log", nil))
if rec.Code != http.StatusOK {
t.Fatalf("status = %d", rec.Code)
}
var response struct {
Entries []notifications.DeliveryLogEntry `json:"entries"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
t.Fatal(err)
}
if len(response.Entries) != 1 || response.Entries[0].ErrorMessage != tc.want ||
response.Entries[0].NotificationID != "attempt-1" || response.Entries[0].FailureClass != "transport" {
t.Fatalf("unexpected delivery projection: %#v", response)
}
manager.AssertExpectations(t)
monitor.AssertExpectations(t)
})
}
}
+50
View File
@@ -38,6 +38,7 @@ import (
"github.com/rcourtman/pulse-go-rewrite/internal/ai/unified"
"github.com/rcourtman/pulse-go-rewrite/internal/alerts"
"github.com/rcourtman/pulse-go-rewrite/internal/api/agenttokens"
"github.com/rcourtman/pulse-go-rewrite/internal/api/alerting"
"github.com/rcourtman/pulse-go-rewrite/internal/api/chartapi"
"github.com/rcourtman/pulse-go-rewrite/internal/api/configapi"
"github.com/rcourtman/pulse-go-rewrite/internal/api/resourceapi"
@@ -46,6 +47,7 @@ import (
"github.com/rcourtman/pulse-go-rewrite/internal/mock"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
"github.com/rcourtman/pulse-go-rewrite/internal/monitoring"
"github.com/rcourtman/pulse-go-rewrite/internal/notifications"
"github.com/rcourtman/pulse-go-rewrite/internal/operationreceipt"
"github.com/rcourtman/pulse-go-rewrite/internal/recovery"
"github.com/rcourtman/pulse-go-rewrite/internal/relay"
@@ -24731,3 +24733,51 @@ func TestSecurityStatusCurrentUserForScopedLocalSession(t *testing.T) {
}
}
}
// Embedding the interface makes any unexpected mutation fail rather than
// supplying no-op write methods to this read-only API contract fixture.
type diagnosticContractManager struct{ alerting.NotificationManager }
func (diagnosticContractManager) GetDeliveryLog(time.Time, int) ([]notifications.DeliveryLogEntry, error) {
return []notifications.DeliveryLogEntry{{
NotificationID: "attempt-1", DestinationID: "ops", Outcome: notifications.DeliveryOutcomeFailed,
ErrorMessage: "post https://example.test/hook?token=fixture-secret returned 401",
FailureClass: "authentication", Attempts: 3,
}}, nil
}
type diagnosticContractMonitor struct{ alerting.NotificationMonitor }
func (diagnosticContractMonitor) GetNotificationManager() alerting.NotificationManager {
return diagnosticContractManager{}
}
func TestContract_DeliveryDiagnosticPayload(t *testing.T) {
rec := httptest.NewRecorder()
alerting.NewNotificationHandlers(nil, diagnosticContractMonitor{}).GetDeliveryLog(rec,
httptest.NewRequest(http.MethodGet, "/api/notifications/delivery-log", nil))
if rec.Code != http.StatusOK {
t.Fatalf("status = %d", rec.Code)
}
var response struct {
Entries []map[string]interface{} `json:"entries"`
}
if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
t.Fatal(err)
}
if len(response.Entries) != 1 {
t.Fatalf("entry count = %d", len(response.Entries))
}
for key, want := range map[string]interface{}{
"notificationId": "attempt-1", "destinationId": "ops", "outcome": "failed",
"failureClass": "authentication", "attempts": float64(3),
"errorMessage": "post https://example.test/hook?token=REDACTED returned 401",
} {
if got := response.Entries[0][key]; got != want {
t.Errorf("%s = %v, want %v", key, got, want)
}
}
if strings.Contains(rec.Body.String(), "fixture-secret") {
t.Fatal("response exposed destination credential")
}
}