diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 0114ce37..c486f6de 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.92.0" + ".": "0.93.0" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index f62b102b..3a4b9b3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## [0.93.0](https://github.com/kernel/kernel-go-sdk/compare/v0.92.0...v0.93.0) (2026-08-19) + + +### Features + +* Add proxy_error to browser telemetry event schema ([467fea7](https://github.com/kernel/kernel-go-sdk/commit/467fea72ee93a0d8e4a520169cf5e14e5d4076ee)) +* Bind managed auth submissions to interactions ([796d424](https://github.com/kernel/kernel-go-sdk/commit/796d4245c87a39acbb0d408b05f0de830c500772)) +* Harden canonical managed auth interactions ([6e62bf5](https://github.com/kernel/kernel-go-sdk/commit/6e62bf5b91e5d315b90b6c9c7296e09e312fb338)) + ## [0.92.0](https://github.com/kernel/kernel-go-sdk/compare/v0.91.0...v0.92.0) (2026-08-17) diff --git a/README.md b/README.md index cd9fc575..9d3fe382 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ Or to pin the version: ```sh -go get -u 'github.com/kernel/kernel-go-sdk@v0.92.0' +go get -u 'github.com/kernel/kernel-go-sdk@v0.93.0' ``` diff --git a/api.md b/api.md index 463c4dc0..17139df9 100644 --- a/api.md +++ b/api.md @@ -143,6 +143,7 @@ Response Types: - kernel.BrowserPageNavigationEvent - kernel.BrowserPageNavigationSettledEvent - kernel.BrowserPageTabOpenedEvent +- kernel.BrowserProxyErrorEvent - kernel.BrowserServiceCrashedEvent - kernel.BrowserSystemOomKillEvent - kernel.BrowserTelemetryCategoriesConfig diff --git a/authconnection.go b/authconnection.go index 8a1138a3..1e309829 100644 --- a/authconnection.go +++ b/authconnection.go @@ -399,6 +399,9 @@ type ManagedAuth struct { HealthChecks bool `json:"health_checks"` // URL to redirect user to for hosted login (present when flow in progress) HostedURL string `json:"hosted_url" api:"nullable" format:"uri"` + // Opaque identifier for the current canonical interaction. Required when + // submitting fields or choices and changes for each new actionable pause. + InteractionID string `json:"interaction_id" api:"nullable"` // Deprecated alias for `last_auth_check_at`. Despite the name, this is the last // health-check timestamp, not the last successful authentication. Use // `last_auth_check_at` instead. @@ -466,6 +469,7 @@ type ManagedAuth struct { HealthCheckInterval respjson.Field HealthChecks respjson.Field HostedURL respjson.Field + InteractionID respjson.Field LastAuthAt respjson.Field LastAuthCheckAt respjson.Field LiveViewURL respjson.Field @@ -784,6 +788,10 @@ func (r *ManagedAuthDiscoveredField) UnmarshalJSON(data []byte) error { type ManagedAuthField struct { // Stable field identifier for canonical submit. ID string `json:"id" api:"required"` + // Why the field requires user input. + // + // Any of "missing", "rejected". + Reason string `json:"reason" api:"required"` // Credential reference name to store the submitted value under. Ref string `json:"ref" api:"required"` // Managed-auth field type. @@ -796,20 +804,17 @@ type ManagedAuthField struct { Label string `json:"label"` // Selector for the visible field, when available. ObservedSelector string `json:"observed_selector" api:"nullable"` - // Whether the submitted value must replace an existing credential after explicit - // rejection. - ReplaceExisting bool `json:"replace_existing"` // Whether this field is required. Required bool `json:"required"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { ID respjson.Field + Reason respjson.Field Ref respjson.Field Type respjson.Field Hint respjson.Field Label respjson.Field ObservedSelector respjson.Field - ReplaceExisting respjson.Field Required respjson.Field ExtraFields map[string]respjson.Field raw string @@ -1726,6 +1731,9 @@ func (r *ManagedAuthUpdateRequestProxyParam) UnmarshalJSON(data []byte) error { // fields/sso_button_selector/sso_provider/mfa_option_id/sign_in_option_id remain // supported during deprecation. type SubmitFieldsRequestParam struct { + // Opaque interaction ID returned with canonical fields and choices. Required for + // canonical submissions. + InteractionID param.Opt[string] `json:"interaction_id,omitzero"` // The MFA method type to select (when mfa_options were returned) MfaOptionID param.Opt[string] `json:"mfa_option_id,omitzero"` // Canonical choice ID selected by the user. @@ -1804,6 +1812,8 @@ type AuthConnectionFollowResponseUnion struct { // This field is from variant [AuthConnectionFollowResponseManagedAuthState]. HostedURL string `json:"hosted_url"` // This field is from variant [AuthConnectionFollowResponseManagedAuthState]. + InteractionID string `json:"interaction_id"` + // This field is from variant [AuthConnectionFollowResponseManagedAuthState]. LiveViewURL string `json:"live_view_url"` // This field is from variant [AuthConnectionFollowResponseManagedAuthState]. MfaOptions []AuthConnectionFollowResponseManagedAuthStateMfaOption `json:"mfa_options"` @@ -1830,6 +1840,7 @@ type AuthConnectionFollowResponseUnion struct { Fields respjson.Field FlowType respjson.Field HostedURL respjson.Field + InteractionID respjson.Field LiveViewURL respjson.Field MfaOptions respjson.Field PendingSSOButtons respjson.Field @@ -1930,6 +1941,9 @@ type AuthConnectionFollowResponseManagedAuthState struct { FlowType string `json:"flow_type"` // URL to redirect user to for hosted login. HostedURL string `json:"hosted_url" format:"uri"` + // Opaque identifier for the current canonical interaction. Required when + // submitting fields or choices and changes for each new actionable pause. + InteractionID string `json:"interaction_id"` // Browser live view URL for debugging. LiveViewURL string `json:"live_view_url" format:"uri"` // MFA method options (present when flow_step=AWAITING_INPUT; may also be present @@ -1961,6 +1975,7 @@ type AuthConnectionFollowResponseManagedAuthState struct { Fields respjson.Field FlowType respjson.Field HostedURL respjson.Field + InteractionID respjson.Field LiveViewURL respjson.Field MfaOptions respjson.Field PendingSSOButtons respjson.Field @@ -2078,6 +2093,10 @@ func (r *AuthConnectionFollowResponseManagedAuthStateDiscoveredField) UnmarshalJ type AuthConnectionFollowResponseManagedAuthStateField struct { // Stable field identifier for canonical submit. ID string `json:"id" api:"required"` + // Why the field requires user input. + // + // Any of "missing", "rejected". + Reason string `json:"reason" api:"required"` // Credential reference name to store the submitted value under. Ref string `json:"ref" api:"required"` // Managed-auth field type. @@ -2090,20 +2109,17 @@ type AuthConnectionFollowResponseManagedAuthStateField struct { Label string `json:"label"` // Selector for the visible field, when available. ObservedSelector string `json:"observed_selector" api:"nullable"` - // Whether the submitted value must replace an existing credential after explicit - // rejection. - ReplaceExisting bool `json:"replace_existing"` // Whether this field is required. Required bool `json:"required"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { ID respjson.Field + Reason respjson.Field Ref respjson.Field Type respjson.Field Hint respjson.Field Label respjson.Field ObservedSelector respjson.Field - ReplaceExisting respjson.Field Required respjson.Field ExtraFields map[string]respjson.Field raw string diff --git a/authconnection_test.go b/authconnection_test.go index aefb2ba3..361ddf30 100644 --- a/authconnection_test.go +++ b/authconnection_test.go @@ -509,6 +509,7 @@ func TestAuthConnectionSubmitWithOptionalParams(t *testing.T) { "email": "user@example.com", "password": "secret", }, + InteractionID: kernel.String("mai_abc123xyz"), MfaOptionID: kernel.String("sms"), SelectedChoiceID: kernel.String("google"), SignInOptionID: kernel.String("work-account"), diff --git a/browsertelemetry.go b/browsertelemetry.go index bceb98b7..55581a3f 100644 --- a/browsertelemetry.go +++ b/browsertelemetry.go @@ -1925,6 +1925,86 @@ func (r *BrowserPageTabOpenedEventData) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } +// A branded proxy-layer failure observed by the browser. Emitted when the metro +// egress host-proxy serves a branded 5xx error page whose response carries the +// X-Kernel-Proxy-Error header. Low-volume and carries a typed code. Its value is +// per-session and per-URL attribution for sessions that already capture the +// network stream: proxy failures are only observable while the CDP network +// collector is running, so this is an opt-in refinement of the raw network events +// rather than a default-on alerting signal. +type BrowserProxyErrorEvent struct { + Category constant.Network `json:"category" default:"network"` + // Provenance metadata identifying which producer emitted the event. + Source BrowserEventSource `json:"source" api:"required"` + // Event timestamp in Unix microseconds. + Ts int64 `json:"ts" api:"required"` + Type constant.ProxyError `json:"type" default:"proxy_error"` + // Browser event context stamped by the browser monitor onto all CDP-sourced + // events. Identifies the target, frame, and navigation epoch in which the event + // occurred. + Data BrowserProxyErrorEventData `json:"data"` + // True if the data field was truncated due to size limits. + Truncated bool `json:"truncated"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Category respjson.Field + Source respjson.Field + Ts respjson.Field + Type respjson.Field + Data respjson.Field + Truncated respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r BrowserProxyErrorEvent) RawJSON() string { return r.JSON.raw } +func (r *BrowserProxyErrorEvent) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Browser event context stamped by the browser monitor onto all CDP-sourced +// events. Identifies the target, frame, and navigation epoch in which the event +// occurred. +type BrowserProxyErrorEventData struct { + // Proxy-layer error code: the X-Kernel-Proxy-Error response header value from a + // branded 5xx error page served by the metro egress host-proxy. Values mirror what + // the proxy emits: destination_blocked, provider_blacklisted, + // provider_unreachable, proxy_unavailable, upstream_timeout, upstream_dns_failure, + // upstream_connect_failed. Unknown header values are dropped. + // + // Any of "destination_blocked", "provider_blacklisted", "provider_unreachable", + // "proxy_unavailable", "upstream_timeout", "upstream_dns_failure", + // "upstream_connect_failed". + Code string `json:"code" api:"required"` + // CDP request identifier matching the originating request. + RequestID string `json:"request_id" api:"required"` + // HTTP response status of the branded error page (502). + Status int64 `json:"status" api:"required"` + // HTTP method of the failed request, when known. + Method string `json:"method"` + // CDP Network.ResourceType for the request, when known. + ResourceType string `json:"resource_type"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Code respjson.Field + RequestID respjson.Field + Status respjson.Field + Method respjson.Field + ResourceType respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` + BrowserEventContext +} + +// Returns the unmodified JSON received from the API +func (r BrowserProxyErrorEventData) RawJSON() string { return r.JSON.raw } +func (r *BrowserProxyErrorEventData) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + // A managed service exited unexpectedly. Intentional stops do not produce this // event; only unexpected exits and terminal restart-give-up transitions do. type BrowserServiceCrashedEvent struct { @@ -2277,15 +2357,16 @@ func (r *BrowserTelemetryConfig) UnmarshalJSON(data []byte) error { // [BrowserConsoleLogEvent], [BrowserConsoleErrorEvent], // [BrowserNetworkRequestEvent], [BrowserNetworkResponseEvent], // [BrowserNetworkLoadingFailedEvent], [BrowserNetworkIdleEvent], -// [BrowserPageNavigationEvent], [BrowserPageDomContentLoadedEvent], -// [BrowserPageLoadEvent], [BrowserPageTabOpenedEvent], -// [BrowserPageLayoutShiftEvent], [BrowserPageLcpEvent], -// [BrowserPageLayoutSettledEvent], [BrowserPageNavigationSettledEvent], -// [BrowserInteractionClickEvent], [BrowserInteractionKeyEvent], -// [BrowserInteractionScrollSettledEvent], [BrowserMonitorScreenshotEvent], -// [BrowserMonitorDisconnectedEvent], [BrowserMonitorReconnectedEvent], -// [BrowserMonitorReconnectFailedEvent], [BrowserMonitorInitFailedEvent], -// [BrowserAPICallEvent], [BrowserCdpConnectEvent], [BrowserCdpDisconnectEvent], +// [BrowserProxyErrorEvent], [BrowserPageNavigationEvent], +// [BrowserPageDomContentLoadedEvent], [BrowserPageLoadEvent], +// [BrowserPageTabOpenedEvent], [BrowserPageLayoutShiftEvent], +// [BrowserPageLcpEvent], [BrowserPageLayoutSettledEvent], +// [BrowserPageNavigationSettledEvent], [BrowserInteractionClickEvent], +// [BrowserInteractionKeyEvent], [BrowserInteractionScrollSettledEvent], +// [BrowserMonitorScreenshotEvent], [BrowserMonitorDisconnectedEvent], +// [BrowserMonitorReconnectedEvent], [BrowserMonitorReconnectFailedEvent], +// [BrowserMonitorInitFailedEvent], [BrowserAPICallEvent], +// [BrowserCdpConnectEvent], [BrowserCdpDisconnectEvent], // [BrowserLiveViewConnectEvent], [BrowserLiveViewDisconnectEvent], // [BrowserCaptchaSolveResultEvent], [BrowserSystemOomKillEvent], // [BrowserServiceCrashedEvent]. @@ -2299,7 +2380,7 @@ type BrowserTelemetryEventUnion struct { Source BrowserEventSource `json:"source"` Ts int64 `json:"ts"` // Any of "console_log", "console_error", "network_request", "network_response", - // "network_loading_failed", "network_idle", "page_navigation", + // "network_loading_failed", "network_idle", "proxy_error", "page_navigation", // "page_dom_content_loaded", "page_load", "page_tab_opened", "page_layout_shift", // "page_lcp", "page_layout_settled", "page_navigation_settled", // "interaction_click", "interaction_key", "interaction_scroll_settled", @@ -2311,17 +2392,18 @@ type BrowserTelemetryEventUnion struct { // This field is a union of [BrowserConsoleLogEventData], // [BrowserConsoleErrorEventData], [BrowserNetworkRequestEventData], // [BrowserNetworkResponseEventData], [BrowserNetworkLoadingFailedEventData], - // [BrowserEventContext], [BrowserPageNavigationEventData], - // [BrowserPageDomContentLoadedEventData], [BrowserPageLoadEventData], - // [BrowserPageTabOpenedEventData], [BrowserPageLayoutShiftEventData], - // [BrowserPageLcpEventData], [BrowserInteractionClickEventData], - // [BrowserInteractionKeyEventData], [BrowserInteractionScrollSettledEventData], - // [BrowserMonitorScreenshotEventData], [BrowserMonitorDisconnectedEventData], - // [BrowserMonitorReconnectedEventData], [BrowserMonitorReconnectFailedEventData], - // [BrowserMonitorInitFailedEventData], [BrowserAPICallEventData], - // [BrowserCdpDisconnectEventData], [BrowserLiveViewConnectEventData], - // [BrowserLiveViewDisconnectEventData], [BrowserCaptchaSolveResultEventData], - // [BrowserSystemOomKillEventData], [BrowserServiceCrashedEventData] + // [BrowserEventContext], [BrowserProxyErrorEventData], + // [BrowserPageNavigationEventData], [BrowserPageDomContentLoadedEventData], + // [BrowserPageLoadEventData], [BrowserPageTabOpenedEventData], + // [BrowserPageLayoutShiftEventData], [BrowserPageLcpEventData], + // [BrowserInteractionClickEventData], [BrowserInteractionKeyEventData], + // [BrowserInteractionScrollSettledEventData], [BrowserMonitorScreenshotEventData], + // [BrowserMonitorDisconnectedEventData], [BrowserMonitorReconnectedEventData], + // [BrowserMonitorReconnectFailedEventData], [BrowserMonitorInitFailedEventData], + // [BrowserAPICallEventData], [BrowserCdpDisconnectEventData], + // [BrowserLiveViewConnectEventData], [BrowserLiveViewDisconnectEventData], + // [BrowserCaptchaSolveResultEventData], [BrowserSystemOomKillEventData], + // [BrowserServiceCrashedEventData] Data BrowserTelemetryEventUnionData `json:"data"` Truncated bool `json:"truncated"` JSON struct { @@ -2348,6 +2430,7 @@ func (BrowserNetworkRequestEvent) implBrowserTelemetryEventUnion() {} func (BrowserNetworkResponseEvent) implBrowserTelemetryEventUnion() {} func (BrowserNetworkLoadingFailedEvent) implBrowserTelemetryEventUnion() {} func (BrowserNetworkIdleEvent) implBrowserTelemetryEventUnion() {} +func (BrowserProxyErrorEvent) implBrowserTelemetryEventUnion() {} func (BrowserPageNavigationEvent) implBrowserTelemetryEventUnion() {} func (BrowserPageDomContentLoadedEvent) implBrowserTelemetryEventUnion() {} func (BrowserPageLoadEvent) implBrowserTelemetryEventUnion() {} @@ -2382,6 +2465,7 @@ func (BrowserServiceCrashedEvent) implBrowserTelemetryEventUnion() {} // case kernel.BrowserNetworkResponseEvent: // case kernel.BrowserNetworkLoadingFailedEvent: // case kernel.BrowserNetworkIdleEvent: +// case kernel.BrowserProxyErrorEvent: // case kernel.BrowserPageNavigationEvent: // case kernel.BrowserPageDomContentLoadedEvent: // case kernel.BrowserPageLoadEvent: @@ -2423,6 +2507,8 @@ func (u BrowserTelemetryEventUnion) AsAny() anyBrowserTelemetryEvent { return u.AsNetworkLoadingFailed() case "network_idle": return u.AsNetworkIdle() + case "proxy_error": + return u.AsProxyError() case "page_navigation": return u.AsPageNavigation() case "page_dom_content_loaded": @@ -2505,6 +2591,11 @@ func (u BrowserTelemetryEventUnion) AsNetworkIdle() (v BrowserNetworkIdleEvent) return } +func (u BrowserTelemetryEventUnion) AsProxyError() (v BrowserProxyErrorEvent) { + apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) + return +} + func (u BrowserTelemetryEventUnion) AsPageNavigation() (v BrowserPageNavigationEvent) { apijson.UnmarshalRoot(json.RawMessage(u.JSON.raw), &v) return @@ -2644,10 +2735,11 @@ type BrowserTelemetryEventUnionData struct { // This field is from variant [BrowserConsoleLogEventData], // [BrowserConsoleErrorEventData], [BrowserNetworkRequestEventData], // [BrowserNetworkResponseEventData], [BrowserNetworkLoadingFailedEventData], - // [BrowserEventContext], [BrowserPageDomContentLoadedEventData], - // [BrowserPageLoadEventData], [BrowserPageLayoutShiftEventData], - // [BrowserPageLcpEventData], [BrowserInteractionClickEventData], - // [BrowserInteractionKeyEventData], [BrowserInteractionScrollSettledEventData]. + // [BrowserEventContext], [BrowserProxyErrorEventData], + // [BrowserPageDomContentLoadedEventData], [BrowserPageLoadEventData], + // [BrowserPageLayoutShiftEventData], [BrowserPageLcpEventData], + // [BrowserInteractionClickEventData], [BrowserInteractionKeyEventData], + // [BrowserInteractionScrollSettledEventData]. NavSeq int64 `json:"nav_seq"` SessionID string `json:"session_id"` TargetID string `json:"target_id"` @@ -2683,7 +2775,7 @@ type BrowserTelemetryEventUnionData struct { Body string `json:"body"` // This field is from variant [BrowserNetworkResponseEventData]. MimeType string `json:"mime_type"` - // This field is a union of [int64], [int64], [string] + // This field is a union of [int64], [int64], [int64], [string] Status BrowserTelemetryEventUnionDataStatus `json:"status"` // This field is from variant [BrowserNetworkResponseEventData]. StatusText string `json:"status_text"` @@ -2691,6 +2783,8 @@ type BrowserTelemetryEventUnionData struct { Canceled bool `json:"canceled"` // This field is from variant [BrowserNetworkLoadingFailedEventData]. ErrorText string `json:"error_text"` + // This field is from variant [BrowserProxyErrorEventData]. + Code string `json:"code"` // This field is from variant [BrowserPageNavigationEventData]. ParentFrameID string `json:"parent_frame_id"` CdpTimestamp float64 `json:"cdp_timestamp"` @@ -2797,6 +2891,7 @@ type BrowserTelemetryEventUnionData struct { StatusText respjson.Field Canceled respjson.Field ErrorText respjson.Field + Code respjson.Field ParentFrameID respjson.Field CdpTimestamp respjson.Field OpenerID respjson.Field diff --git a/internal/version.go b/internal/version.go index 7892089e..6cb9969a 100644 --- a/internal/version.go +++ b/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "0.92.0" // x-release-please-version +const PackageVersion = "0.93.0" // x-release-please-version diff --git a/shared/constant/constants.go b/shared/constant/constants.go index 30dd2fd5..a6bd9e37 100644 --- a/shared/constant/constants.go +++ b/shared/constant/constants.go @@ -61,6 +61,7 @@ type PageLoad string // Always "page_load" type PageNavigation string // Always "page_navigation" type PageNavigationSettled string // Always "page_navigation_settled" type PageTabOpened string // Always "page_tab_opened" +type ProxyError string // Always "proxy_error" type Screenshot string // Always "screenshot" type ServiceCrashed string // Always "service_crashed" type SseHeartbeat string // Always "sse_heartbeat" @@ -112,6 +113,7 @@ func (c PageLoad) Default() PageLoad { return "page_ func (c PageNavigation) Default() PageNavigation { return "page_navigation" } func (c PageNavigationSettled) Default() PageNavigationSettled { return "page_navigation_settled" } func (c PageTabOpened) Default() PageTabOpened { return "page_tab_opened" } +func (c ProxyError) Default() ProxyError { return "proxy_error" } func (c Screenshot) Default() Screenshot { return "screenshot" } func (c ServiceCrashed) Default() ServiceCrashed { return "service_crashed" } func (c SseHeartbeat) Default() SseHeartbeat { return "sse_heartbeat" } @@ -161,6 +163,7 @@ func (c PageLoad) MarshalJSON() ([]byte, error) { return marshal func (c PageNavigation) MarshalJSON() ([]byte, error) { return marshalString(c) } func (c PageNavigationSettled) MarshalJSON() ([]byte, error) { return marshalString(c) } func (c PageTabOpened) MarshalJSON() ([]byte, error) { return marshalString(c) } +func (c ProxyError) MarshalJSON() ([]byte, error) { return marshalString(c) } func (c Screenshot) MarshalJSON() ([]byte, error) { return marshalString(c) } func (c ServiceCrashed) MarshalJSON() ([]byte, error) { return marshalString(c) } func (c SseHeartbeat) MarshalJSON() ([]byte, error) { return marshalString(c) }