Crosswake.Shell.DiagnosticExport behaviour (crosswake v0.1.2)

View Source

Behaviour-only transport seam for the diagnostic export contract.

Crosswake.Shell.DiagnosticExport defines a fire-and-forget POST contract for shipping typed, versioned diagnostic envelopes to a host-owned endpoint.

Transport semantics (documented — not implemented here)

  • Method: POST
  • Content-Type: application/json
  • Endpoint: host-owned and host-configured (Crosswake does not own it)
  • No response awaited (fire-and-forget)
  • No Elixir HTTP-sending code ships in this module (D-01/D-03)

The actual HTTP sender is implemented by the native shell (iOS MetricKit / Android ApplicationExitInfo) in Phase 67. This module ships the typed envelope contract and the @callback export/1 the native shell implements.

Allowlist-by-construction redaction (D-11..D-14)

The typed Envelope and NativeDiagnostic structs are the allowlist: they cannot represent forbidden data (raw tokens, payloads, PII). The sanitize/1 function is fail-closed: it rejects any input that contains a forbidden key, an unexpected key, or an out-of-enum value.

Phase 65 proof anchors

  • behaviour_info(:callbacks) must include {:export, 1}
  • forbidden_keys/0allowed_keys/0 (disjoint; 19 forbidden keys)
  • sanitize/1 returns {:error, :redaction_failed} on invalid input
  • No diagnostics.* added to Bridge.Contract.commands/0
  • No HTTP client dependency in the published lib

Summary

Callbacks

Export a typed diagnostic envelope to the host-owned endpoint.

Functions

Returns the declared allowed key set (envelope fields + native_diagnostic fields) as the documented union allowlist. Shares no key with forbidden_keys/0.

Returns the closed exit_reason enum.

Returns the canonical 19-key forbidden set (verbatim from Chimeway.Telemetry).

Returns the closed kind enum.

Returns the closed layer enum: [:native, :web, :bridge].

Constructs an Envelope from a map or keyword list.

Constructs an Envelope, raising on failure. Used for fixture generation.

Constructs a NativeDiagnostic from a map or keyword list.

Returns the closed platform enum: [:ios, :android, :web].

Returns the canonical protocol identifier.

Maps an untrusted input map to a typed Envelope.t() or rejects it.

Returns the schema version string.

Returns the closed source enum: [:metrickit, :app_exit_info].

Converts an Envelope or NativeDiagnostic struct to a string-keyed map.

Callbacks

export(t)

@callback export(Crosswake.Shell.DiagnosticExport.Envelope.t()) :: :ok | {:error, term()}

Export a typed diagnostic envelope to the host-owned endpoint.

This is a fire-and-forget POST contract. No response is awaited. The host/native shell implements this callback; Crosswake ships no Elixir HTTP-sending code.

Functions

allowed_keys()

@spec allowed_keys() :: [atom()]

Returns the declared allowed key set (envelope fields + native_diagnostic fields) as the documented union allowlist. Shares no key with forbidden_keys/0.

Note: sanitize/1 validates the outer input map's keys against the envelope fields; the nested native_diagnostic map is validated against the native-diagnostic fields when coerced into a typed NativeDiagnostic.

exit_reasons()

@spec exit_reasons() :: [atom()]

Returns the closed exit_reason enum.

forbidden_keys()

@spec forbidden_keys() :: [atom()]

Returns the canonical 19-key forbidden set (verbatim from Chimeway.Telemetry).

kinds()

@spec kinds() :: [atom()]

Returns the closed kind enum.

layers()

@spec layers() :: [atom()]

Returns the closed layer enum: [:native, :web, :bridge].

new_envelope(attrs)

@spec new_envelope(map() | keyword()) ::
  {:ok, Crosswake.Shell.DiagnosticExport.Envelope.t()} | {:error, keyword()}

Constructs an Envelope from a map or keyword list.

Returns {:ok, %Envelope{}} on success, {:error, keyword()} on failure.

new_envelope!(attrs)

@spec new_envelope!(map() | keyword()) ::
  Crosswake.Shell.DiagnosticExport.Envelope.t()

Constructs an Envelope, raising on failure. Used for fixture generation.

new_native_diagnostic(attrs)

@spec new_native_diagnostic(map() | keyword()) ::
  {:ok, Crosswake.Shell.DiagnosticExport.NativeDiagnostic.t()}
  | {:error, keyword()}

Constructs a NativeDiagnostic from a map or keyword list.

platforms()

@spec platforms() :: [atom()]

Returns the closed platform enum: [:ios, :android, :web].

protocol()

@spec protocol() :: String.t()

Returns the canonical protocol identifier.

sanitize(input)

@spec sanitize(map()) ::
  {:ok, Crosswake.Shell.DiagnosticExport.Envelope.t()}
  | {:error, :redaction_failed}

Maps an untrusted input map to a typed Envelope.t() or rejects it.

Fail-closed (D-14): returns {:error, :redaction_failed} when:

  • input is not a map
  • any key normalizes to a key in forbidden_keys/0
  • any key is outside allowed_keys/0 (unexpected key)
  • any enum field has an out-of-range value
  • any required field is missing or invalid

Does NOT drop-and-continue. A rejected payload is better than a silently truncated partial record.

schema_version()

@spec schema_version() :: String.t()

Returns the schema version string.

sources()

@spec sources() :: [atom()]

Returns the closed source enum: [:metrickit, :app_exit_info].

to_map(envelope)

Converts an Envelope or NativeDiagnostic struct to a string-keyed map.

Stringifies all atom values (:native"native"), rejects nil values and empty maps, and recurses into nested %NativeDiagnostic{} structs. No @derive Jason.Encoder is used.