Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: hookdeck/outpost
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.0.2
Choose a base ref
...
head repository: hookdeck/outpost
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.0.3
Choose a head ref
  • 16 commits
  • 353 files changed
  • 7 contributors

Commits on May 1, 2026

  1. ci: Update outpost version in compose.yml to 1.0.2 (#884)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
    github-actions[bot] and github-actions[bot] authored May 1, 2026
    Configuration menu
    Copy the full SHA
    2d067c3 View commit details
    Browse the repository at this point in the history
  2. chore: 🐝 Update SDK - Generate OUTPOST-GO 1.1.0 (#885)

    * `Outpost.Destinations.Create()`: 
      *  `request.Body.union(kafka)` **Added**
      *  `response.union(kafka)` **Added** (Breaking ⚠️)
    * `Outpost.Destinations.Disable()`:  `response.union(kafka)` **Added** (Breaking ⚠️)
    * `Outpost.Events.List()`:  `request.Request` **Changed** (Breaking ⚠️)
    * `Outpost.Attempts.List()`: 
      *  `request.Request` **Changed** (Breaking ⚠️)
      *  `response.Models[].Destination.union(kafka)` **Added** (Breaking ⚠️)
    * `Outpost.Attempts.Get()`:  `response.Destination.union(kafka)` **Added** (Breaking ⚠️)
    * `Outpost.Destinations.List()`:  `response.[].union(kafka)` **Added** (Breaking ⚠️)
    * `Outpost.Metrics.GetAttemptMetrics()`:  `request.Request` **Changed** (Breaking ⚠️)
    * `Outpost.Destinations.Update()`: 
      *  `request.Body.union(DestinationUpdateKafka)` **Added**
      *  `response.union(Destination).union(kafka)` **Added** (Breaking ⚠️)
    * `Outpost.Metrics.GetEventMetrics()`:  `request.Request` **Changed** (Breaking ⚠️)
    * `Outpost.Destinations.Enable()`:  `response.union(kafka)` **Added** (Breaking ⚠️)
    * `Outpost.Destinations.Get()`:  `response.union(kafka)` **Added** (Breaking ⚠️)
    * `Outpost.Destinations.ListAttempts()`: 
      *  `request.Request` **Changed** (Breaking ⚠️)
      *  `response.Models[].Destination.union(kafka)` **Added** (Breaking ⚠️)
    * `Outpost.Destinations.GetAttempt()`:  `response.Destination.union(kafka)` **Added** (Breaking ⚠️)
    * `Outpost.Retry.Retry()`: **Added**
    * `Outpost.Attempts.Retry()`: **Removed** (Breaking ⚠️)
    
    Co-authored-by: speakeasybot <bot@speakeasyapi.dev>
    github-actions[bot] and speakeasybot authored May 1, 2026
    Configuration menu
    Copy the full SHA
    c665fd3 View commit details
    Browse the repository at this point in the history
  3. chore(sdk): update outpost-go shields.json to version v1.1.0 (#886)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
    github-actions[bot] and github-actions[bot] authored May 1, 2026
    Configuration menu
    Copy the full SHA
    9155606 View commit details
    Browse the repository at this point in the history
  4. chore: 🐝 Update SDK - Generate OUTPOST-PYTHON 1.1.0 (#887)

    * `outpost.destinations.create()`: 
      *  `request.body.union(kafka)` **Added**
      *  `response.union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.destinations.disable()`:  `response.union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.events.list()`:  `request` **Changed** (Breaking ⚠️)
    * `outpost.attempts.list()`: 
      *  `request` **Changed** (Breaking ⚠️)
      *  `response.models[].destination.union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.attempts.get()`:  `response.destination.union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.destinations.list()`:  `response.[].union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.metrics.get_attempt_metrics()`:  `request` **Changed** (Breaking ⚠️)
    * `outpost.destinations.update()`: 
      *  `request.body.union(DestinationUpdateKafka)` **Added**
      *  `response.union(Destination).union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.metrics.get_event_metrics()`:  `request` **Changed** (Breaking ⚠️)
    * `outpost.destinations.enable()`:  `response.union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.destinations.get()`:  `response.union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.destinations.list_attempts()`: 
      *  `request` **Changed** (Breaking ⚠️)
      *  `response.models[].destination.union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.destinations.get_attempt()`:  `response.destination.union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.retry.retry()`: **Added**
    * `outpost.attempts.retry()`: **Removed** (Breaking ⚠️)
    
    Co-authored-by: speakeasybot <bot@speakeasyapi.dev>
    github-actions[bot] and speakeasybot authored May 1, 2026
    Configuration menu
    Copy the full SHA
    88c2fcc View commit details
    Browse the repository at this point in the history
  5. chore: 🐝 Update SDK - Generate OUTPOST-TS 1.1.0 (#888)

    * `outpost.destinations.create()`: 
      *  `request.body.union(kafka)` **Added**
      *  `response.union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.destinations.disable()`:  `response.union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.events.list()`:  `request` **Changed** (Breaking ⚠️)
    * `outpost.attempts.list()`: 
      *  `request` **Changed** (Breaking ⚠️)
      *  `response.models[].destination.union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.attempts.get()`:  `response.destination.union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.destinations.list()`:  `response.[].union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.metrics.getAttemptMetrics()`:  `request` **Changed** (Breaking ⚠️)
    * `outpost.destinations.update()`: 
      *  `request.body.union(DestinationUpdateKafka)` **Added**
      *  `response.union(Destination).union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.metrics.getEventMetrics()`:  `request` **Changed** (Breaking ⚠️)
    * `outpost.destinations.enable()`:  `response.union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.destinations.get()`:  `response.union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.destinations.listAttempts()`: 
      *  `request` **Changed** (Breaking ⚠️)
      *  `response.models[].destination.union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.destinations.getAttempt()`:  `response.destination.union(kafka)` **Added** (Breaking ⚠️)
    * `outpost.retry.retry()`: **Added**
    * `outpost.attempts.retry()`: **Removed** (Breaking ⚠️)
    
    Co-authored-by: speakeasybot <bot@speakeasyapi.dev>
    github-actions[bot] and speakeasybot authored May 1, 2026
    Configuration menu
    Copy the full SHA
    48fe03c View commit details
    Browse the repository at this point in the history
  6. chore(sdk): hoist publish and retry to SDK root (#889)

    * chore(sdk): hoist publish and retry to SDK root
    
    Use x-speakeasy-group: "" to lift /publish and /retry off their
    single-operation namespaces, so callers can use sdk.publish() and
    sdk.retry() directly instead of sdk.Publish.Event() / sdk.Retry.Retry().
    
    Verified locally with speakeasy run -t outpost-go: publish.go and
    retry.go namespace files are removed and Publish/Retry become methods
    on the root Outpost client.
    
    Spec-only change; SDKs will regenerate on the next 1.2.0 release.
    
    * chore(sdk): update examples and docs for hoisted publish() call
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * chore: gofmt
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    alexluong and claude authored May 1, 2026
    Configuration menu
    Copy the full SHA
    fa79e0a View commit details
    Browse the repository at this point in the history
  7. chore: 🐝 Update SDK - Generate OUTPOST-GO 1.2.0 (#890)

    * `Outpost.Publish()`: **Added**
    * `Outpost.Retry()`: **Added**
    * `Outpost.Publish.Event()`: **Removed** (Breaking ⚠️)
    * `Outpost.Retry.Retry()`: **Removed** (Breaking ⚠️)
    
    Co-authored-by: speakeasybot <bot@speakeasyapi.dev>
    github-actions[bot] and speakeasybot authored May 1, 2026
    Configuration menu
    Copy the full SHA
    bc014ba View commit details
    Browse the repository at this point in the history
  8. chore(sdk): update outpost-go shields.json to version v1.2.0 (#891)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
    github-actions[bot] and github-actions[bot] authored May 1, 2026
    Configuration menu
    Copy the full SHA
    f740dde View commit details
    Browse the repository at this point in the history
  9. chore: 🐝 Update SDK - Generate OUTPOST-PYTHON 1.2.0 (#892)

    * `outpost.publish()`: **Added**
    * `outpost.retry()`: **Added**
    * `outpost.publish.event()`: **Removed** (Breaking ⚠️)
    * `outpost.retry.retry()`: **Removed** (Breaking ⚠️)
    
    Co-authored-by: speakeasybot <bot@speakeasyapi.dev>
    github-actions[bot] and speakeasybot authored May 1, 2026
    Configuration menu
    Copy the full SHA
    c8f3f39 View commit details
    Browse the repository at this point in the history
  10. chore: 🐝 Update SDK - Generate OUTPOST-TS 1.2.0 (#893)

    * `outpost.publish()`: **Added**
    * `outpost.retry()`: **Added**
    * `outpost.publish.event()`: **Removed** (Breaking ⚠️)
    * `outpost.retry.retry()`: **Removed** (Breaking ⚠️)
    
    Co-authored-by: speakeasybot <bot@speakeasyapi.dev>
    github-actions[bot] and speakeasybot authored May 1, 2026
    Configuration menu
    Copy the full SHA
    b0e749e View commit details
    Browse the repository at this point in the history

Commits on May 5, 2026

  1. feat(logging): add node identifier to log lines (#894)

    * feat(logging): add node identifier to log lines
    
    Closes #307.
    
    Adds an `os.Hostname()`-derived `node` field to every log line so multi-node
    deployments can be traced from log output alone. Per the issue body,
    `os.Hostname()` returns sensible identifiers across the relevant deployment
    targets:
    
    - Kubernetes: pod name (e.g. `outpost-api-5bbd447f65-x2444`)
    - Docker: container ID
    - ECS: task/container ID
    - Cloud VMs: instance hostname
    
    The hostname is captured once inside `makeLogger` and attached as a default
    zap field via `zapLogger.With(...)`. Both the main logger and the audit
    logger pick this up because they share the same construction path. otelzap
    preserves zap `With` fields, so `Ctx(ctx)` derivations also carry the node
    identifier.
    
    If `os.Hostname()` returns an error, the field falls back to `"unknown"`.
    This matches the silent-fallback pattern in `internal/redislock/redislock.go`
    and avoids failing logger construction on observability metadata.
    
    * feat(logging): use host.name per OTEL convention
    
    Per @alexluong's review: the hostname attribute should follow the OTEL
    host semantic convention to avoid confusion with k8s node semantics.
    mvanhorn authored May 5, 2026
    Configuration menu
    Copy the full SHA
    eed63b0 View commit details
    Browse the repository at this point in the history
  2. feat(api): enable/disable destinations via Create and Update (#895)

    * feat(api): accept created_at/updated_at/disabled_at on destination Create; disabled_at on Update
    
    Enables importing destinations from another system (e.g. Svix migrations) with
    their original timestamps preserved. The Update API also accepts disabled_at
    (omit/null/timestamp) so callers can enable/disable as part of a normal PATCH
    without a separate /enable or /disable round-trip.
    
    Validation: created_at <= updated_at <= now and created_at <= disabled_at <= now.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * feat(api): default created_at to disabled_at when only disabled_at is provided on Create
    
    Importers can now send disabled_at alone without also having to send a
    created_at; the destination's created_at silently mirrors disabled_at in
    that case.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * refactor: drop timestamp validation on Create and Update
    
    Trust import payloads — accept created_at/updated_at/disabled_at as-is
    instead of enforcing future and ordering checks. Importers from other
    systems may carry inconsistent data, and rejecting it would block
    migrations for no real benefit.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * feat(api): reject future disabled_at on Create and Update
    
    The only validation worth keeping — a disabled_at in the future would
    mean "scheduled to disable later," which is not a feature we support.
    Other timestamp fields stay unvalidated; importers can carry whatever
    their source system has.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * feat(api): reject future created_at and updated_at on destination Create
    
    Mirrors the disabled_at future check. None of these timestamps make
    sense in the future at create time.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * feat(api): gate created_at/updated_at on Create to admin (API key) auth
    
    JWT-authenticated tenants can no longer rewrite their own audit
    timestamps; sending created_at or updated_at without admin auth
    returns 403. disabled_at stays accessible to both roles, matching
    the existing /enable and /disable endpoint semantics.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * test: harden 403 coverage for admin-only timestamp fields
    
    - Verify destination is not persisted when 403 is returned
    - Cover both fields together
    - Regression guard: JWT can still create destinations without these fields
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * fix(api): set status field to 422 in validation error responses
    
    ErrorResponse.Parse leaves Code unset when it falls through to the
    default branch (e.g. plain errors.New from handlers), which caused
    AbortWithValidationError to emit {"status":0,...} alongside an HTTP
    422. Set Code at the call site since AbortWithValidationError is
    always 422 by definition; Parse stays caller-agnostic.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    alexluong and claude authored May 5, 2026
    Configuration menu
    Copy the full SHA
    a1d10cc View commit details
    Browse the repository at this point in the history

Commits on May 7, 2026

  1. fix: correct misleading ID prefix field descriptions (#896)

    * fix: correct misleading ID prefix field descriptions
    
    The desc tags for AttemptPrefix, DestinationPrefix, and EventPrefix
    incorrectly stated the prefix is "prepended with underscore". The prefix
    is actually used as-is (verified by idgen tests). Updated descriptions
    to clarify this behavior.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
    
    * fix: clarify prefix descriptions and use cloud example
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    ---------
    
    Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
    alexluong and claude authored May 7, 2026
    Configuration menu
    Copy the full SHA
    dbdefaf View commit details
    Browse the repository at this point in the history

Commits on May 11, 2026

  1. fix: add automated db migrations to docker-compose using an outpost-m…

    …igrate service (#898)
    
    * fix: add automated db migrations to docker-compose using an outpost-migrate service
    
    * chore:changed the name of migration service from 'outpost-migrate' to 'migrate' and added the service in awssqs compose file. Cleaned up some of the trailing whitespaces as well
    distroaryan authored May 11, 2026
    Configuration menu
    Copy the full SHA
    4b87b8c View commit details
    Browse the repository at this point in the history

Commits on May 13, 2026

  1. feat(webhook): proxy-aware error handling for forward proxies (#899)

    * docs(webhook): document forward proxy feature and Envoy support
    
    Adds a Features page covering the existing DESTINATIONS_WEBHOOK_PROXY_URL
    configuration and introduces DESTINATIONS_WEBHOOK_PROXY_TYPE=envoy as an
    opt-in for Envoy-aware error handling: infra-error detection via
    x-envoy-response-flags and the proxyconnect error path, transparent
    redelivery via nack, and x-envoy-*/server header sanitization. Includes
    required Envoy configuration, a minimal reference envoy.yaml, and queue
    retry policy guidance for sufficient redelivery runway during proxy
    outages.
    
    Default behavior for existing users is unchanged.
    
    Co-Authored-By: Claude <noreply@anthropic.com>
    
    * docs(webhook): narrow nack semantics to proxy infra only
    
    Adds an explicit error-attribution table. Nack is reserved for cases
    where the proxy itself is the proximate cause (auth failure, proxy
    unreachable). Destination failures that Envoy merely reports (upstream
    DNS / connect / timeout) are recorded as normal failed delivery attempts
    with destination-attributed codes; the wrapper sanitizes the response
    data so Envoy is not exposed, but the customer still sees a destination
    failure.
    
    This avoids the infinite-nack failure mode for genuinely broken
    destinations.
    
    Co-Authored-By: Claude <noreply@anthropic.com>
    
    * docs(webhook): clarify dead-letter window phrasing
    
    "DLQ runway" replaced with an explicit statement of what the number
    means: the maximum time a single nacked message is retried before
    landing in the dead-letter queue.
    
    Co-Authored-By: Claude <noreply@anthropic.com>
    
    * docs(webhook): drop PROXY_TYPE knob, auto-detect Envoy signals
    
    Removes DESTINATIONS_WEBHOOK_PROXY_TYPE. Envoy-specific signals
    (x-envoy-response-flags, x-envoy-* / server: envoy headers) are detected
    automatically from response headers when present; nothing to configure.
    
    The proxy-infra nack path (proxyconnect 407, dial to proxy fails) is
    generic and applies to any forward proxy, since the discriminator is
    "the proxy is broken or misconfigured," not "the proxy is Envoy."
    
    Co-Authored-By: Claude <noreply@anthropic.com>
    
    * docs(webhook): split error table into generic and Envoy-specific
    
    Reorganizes the error handling section so generic-proxy behavior and
    Envoy-specific signals are in separate tables. Adding support for other
    proxy implementations (Squid, HAProxy, nginx) in the future would
    extend the Envoy support pattern.
    
    Co-Authored-By: Claude <noreply@anthropic.com>
    
    * docs(webhook): clarify sanitization scope and proxy security trade-offs
    
    Two clarifications:
    
    1. Response sanitization is best-effort and currently complete only for
       Envoy. For arbitrary proxies, Outpost can rewrite error messages but
       cannot reliably strip proxy-identifying content from response bodies
       or headers.
    
    2. Production proxy auth + TLS recommendation now depends on topology:
       not required if Outpost and the proxy share a private network;
       strongly recommended if the proxy is reachable over the internet to
       prevent open-relay abuse.
    
    Co-Authored-By: Claude <noreply@anthropic.com>
    
    * docs(webhook): demote production notes to a short paragraph
    
    Trim the production-deployment guidance to a single sentence on the
    VPC-vs-internet trade-off, instead of a subsection with specific
    configuration suggestions.
    
    Co-Authored-By: Claude <noreply@anthropic.com>
    
    * feat(destregistry): add proxy error sentinel types
    
    Defines ErrProxyInfra (signals nack-on-infra-error) and
    ErrProxyDestination (signals record-as-destination-error with mapped
    code) along with MapEnvoyResponseFlag for translating Envoy response
    flags into existing destination error codes (connection_refused,
    timeout, dns_error, network_unreachable).
    
    No consumers yet; subsequent commits add the transport wrapper and
    integration into the webhook delivery path.
    
    Co-Authored-By: Claude <noreply@anthropic.com>
    
    * feat(destwebhook): nack proxy infra errors, classify destination errors
    
    Wraps the proxy transport when a proxy URL is configured. Detects two
    classes of proxy-originated failures:
    
      - Proxy infrastructure (407 / 401 / 403 on CONNECT, dial-to-proxy
        failure): wrapped as ErrProxyInfra. ExecuteHTTPRequest returns
        Delivery: nil so the message handler nacks via the existing
        nil-attempt path (registry.go:179-195), preserving the destination's
        retry budget across proxy outages.
    
      - Destination errors that the proxy reports (5xx on CONNECT, etc.):
        wrapped as ErrProxyDestination with a classification code mapped
        from the response. Falls through to the standard failed-attempt path
        with the explicit code instead of substring-matching the underlying
        error.
    
    CONNECT-time detection uses http.Transport.OnProxyConnectResponse which
    exposes the full response (status + headers) before Go discards it,
    sidestepping the fragility of parsing internal error strings. The
    wrapper still detects dial-to-proxy failures via the "proxyconnect"
    prefix that Go's net.OpError keeps for that case.
    
    Co-Authored-By: Claude <noreply@anthropic.com>
    
    * feat(destwebhook): Envoy response-flag detection and header sanitization
    
    Extends proxyTransport with Envoy-specific signals layered on top of the
    generic proxy handling:
    
      - Plain-HTTP forwarding path: responses carrying a non-empty/non-"-"
        x-envoy-response-flags header are treated as Envoy-synthesized
        failures, converted to ErrProxyDestination with the flag mapped to a
        destination error code, and their bodies dropped so they don't reach
        the delivery record.
    
      - CONNECT path: onProxyConnectResponse reads the same flag header on
        non-200 responses to refine the generic "connection_refused"
        classification (e.g. flag DC -> dns_error, UT -> timeout).
    
      - Successful responses are stripped of x-envoy-* headers and a
        "server: envoy" header before being returned, so the proxy is not
        fingerprinted in the destination's recorded response.
    
    Co-Authored-By: Claude <noreply@anthropic.com>
    
    * test(destregistry): pin Go stdlib "proxyconnect" error wording
    
    Adds a snapshot test asserting that net/http still emits "proxyconnect"
    in dial-to-proxy failure errors. The proxyTransport detector relies on
    this internal stdlib convention; if a Go upgrade changes the wording,
    this test fails in CI rather than letting the detector silently stop
    matching and causing proxy outages to be misclassified as destination
    errors.
    
    Co-Authored-By: Claude <noreply@anthropic.com>
    
    * docs(webhook): align tables with implementation
    
    Two small adjustments after the code landed:
    
      - Drop the implementation-specific "Outpost sees" column from the
        generic table; describe observed behavior instead. Proxy auth-style
        failures cover 407, 401, and 403, not just 407.
    
      - Frame the Envoy table around the response-flag signal directly,
        rather than listing every flag-to-code combination (those are
        documented by the MapEnvoyResponseFlag mapping).
    
    Co-Authored-By: Claude <noreply@anthropic.com>
    
    * docs(webhook): move response_headers_to_add to RouteConfiguration
    
    Envoy rejects response_headers_to_add at the HttpConnectionManager
    level (confirmed on Envoy 1.31). The field belongs on
    RouteConfiguration. Updates both the standalone snippet and the
    reference envoy.yaml.
    
    Co-Authored-By: Claude <noreply@anthropic.com>
    
    * fix(destwebhook): treat HTTPS responses as byte-transparent
    
    Destinations commonly sit behind their own Envoy at the edge, which
    emits server: envoy and x-envoy-response-flags on the response. For
    HTTPS destinations, that response travels through an established CONNECT
    tunnel — the forward proxy is byte-blind to TLS-encrypted bytes and
    cannot have synthesized or modified anything. Previously the wrapper
    treated those headers as if our forward proxy had set them, which:
    
      - converted a real destination 503 from a downed app into a misleading
        "connection_refused" with no body
      - stripped the destination's own envoy observability headers
    
    After this change, HTTPS responses (post-successful-CONNECT) are
    returned unchanged. Proxy-originated HTTPS failures all happen at
    CONNECT time and remain handled in onProxyConnectResponse.
    
    The plain-HTTP forwarding path still applies envoy detection and header
    stripping because the forward proxy is in the byte path on the return.
    The residual case where this over-strips — a plain-HTTP destination
    that is itself behind Envoy — is documented as a limitation;
    attribution remains correct because x-envoy-response-flags is
    overwritten by the forward Envoy via OVERWRITE_IF_EXISTS_OR_ADD.
    
    Top-of-file doc comment on proxyTransport now describes the two
    surfaces (CONNECT-time vs plain-HTTP-forwarding) so the dual-path
    design is discoverable.
    
    Co-Authored-By: Claude <noreply@anthropic.com>
    
    * chore(destwebhook): apply review nits to proxy transport
    
    - httphelper: skip ClassifyNetworkError when ErrProxyDestination sentinel matches
    - proxytransport: drop strings.ToLower; http.Header keys are canonicalized
    - proxytransport: drop impossible nil-guard on req/req.URL in RoundTrip
    - proxytransport: note unhandled Envoy flags fall through to network_error
    - proxytransport: cross-reference pin test from proxyconnect detector
    - proxytransport_test: drop unreachable empty-flag row from map test
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * refactor(destregistry): extract NewHTTPClient as free function
    
    MakeHTTPClient never touched BaseProvider state — it was a free helper
    pretending to be a method. Move it to httpclient.go and update the three
    HTTP-based providers (desthookdeck, destwebhook, destwebhookstandard) to
    call destregistry.NewHTTPClient directly. BaseProvider keeps only the
    metadata/validation concerns it actually owns.
    
    No behavior change.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * refactor(destwebhook): scope proxy transport to webhook package
    
    Move proxytransport.go, the Envoy flag mapping, and all sentinels
    (ErrProxyInfra, ErrProxyDestination, IsProxyInfraError) into the
    destwebhook package — the only place they're actually used. destregistry
    keeps a generic WrapTransport hook on HTTPClientConfig; destwebhook
    exports a WrapTransport function and the two webhook providers
    (destwebhook, destwebhookstandard) plug it in when constructing their
    HTTP client.
    
    destregistry/ no longer has any proxy-specific code. The wrapper, the
    Envoy flag table, the CONNECT-response callback, and all four test files
    move with the sentinels.
    
    No behavior change.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * chore: gofmt baseprovider
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * docs(webhook-proxy): clarify Pub/Sub redelivery runway with OSS defaults
    
    Replace the "set 5s/600s/10 attempts for ~30min runway" recommendation
    with the actual behavior of the default provisioning in
    internal/mqinfra/gcppubsub.go (10s/120s/6 attempts ~ 5min) and note that
    operators expecting longer proxy outages should tune
    MinRetryBackoff/MaxRetryBackoff/RetryLimit.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * fix(destwebhook): map Envoy DF (DNS failure), not DC
    
    DC (Downstream Connection Termination) is the wrong flag — it means the
    client closed the connection, not DNS resolution. The actual flag emitted
    by Envoy's dynamic_forward_proxy when a host fails to resolve is DF (DNS
    Failure). Verified against a local Envoy reference proxy
    (build/dev/envoy/envoy.yaml): nonexistent.invalid → DF → dns_error.
    
    Also adds a local-dev Envoy service (build/dev/compose.yml + envoy.yaml)
    matching the documented config — `%RESPONSE_FLAGS%` on RouteConfiguration
    with OVERWRITE_IF_EXISTS_OR_ADD — so the QA suite at qa/suites/outpost/
    webhook-proxy.md can be run against a real proxy locally.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * feat(destwebhook): expand Envoy flag mapping + capture diagnostics
    
    Address review feedback that the recorded error code is too coarse and
    gives operators nothing to diagnose with.
    
    Mapping: align with ClassifyNetworkError vocabulary so customers see the
    same codes whether or not a proxy is in path.
      - UC, UR, LR → connection_reset (split out of connection_refused)
      - UPE, DPE → protocol_error (new bucket; previously network_error)
      - UMSDR → timeout
      - network_error stays as the catch-all; operator-visible signal that
        a new flag has shown up and the table should be expanded.
    
    Diagnostics: capture raw x-envoy-response-flags and the new
    x-envoy-response-code-details (stage{reason}) on ErrProxyDestination
    and attach them to the publish-attempt error payload as proxy_flag /
    proxy_details. Logged operator-side; never written to the customer-
    visible attempt response_data — same model EG uses.
    
    Ref Envoy config emits both headers with OVERWRITE_IF_EXISTS_OR_ADD so
    destinations can't spoof them.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * refactor(destwebhook): generic Diagnostics map on ErrProxyDestination
    
    Replace Flag/Details fields with a free-form Diagnostics map[string]string.
    The previous shape baked Envoy-specific terminology into a struct that
    classifies errors from any forward proxy — even though only Envoy
    populates the fields today.
    
    Generic map keeps the contract honest: whoever populates a key owns its
    naming (envoy_flag, envoy_details for Envoy; future Squid/HAProxy paths
    would add their own). Error() sorts keys for deterministic log output.
    httphelper splats the map into the publish-attempt Data payload.
    
    Behavior unchanged — same log strings, same customer-invisible surface.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    ---------
    
    Co-authored-by: Claude <noreply@anthropic.com>
    alexluong and claude authored May 13, 2026
    Configuration menu
    Copy the full SHA
    121bd95 View commit details
    Browse the repository at this point in the history
  2. fix: increase consumer error tolerance for transient infra outages (#900

    )
    
    Previously the consumer gave up after 5 consecutive receive errors with
    a 5s backoff cap (~3s total tolerance), permanently killing the worker
    with no recovery path. A brief broker hiccup (e.g. GCP OAuth/DNS blip,
    managed broker restart) was enough to take down logmq/deliverymq workers
    across deployments until containers were manually restarted.
    
    Mirrors the same fix applied to the retrymq scheduler in #881. Increase
    to 10 errors with 15s backoff cap (~1 min tolerance window).
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    alexluong and claude authored May 13, 2026
    Configuration menu
    Copy the full SHA
    2aa4cec View commit details
    Browse the repository at this point in the history
Loading