codebase-cleanup

Run focused, pass-based remediation on an Elixir/BEAM codebase to eliminate unsafe patterns and harden runtime boundaries. Use when auditing or cleaning up OTP supervision, DTO/boundary integrity, atom safety, ambient configuration, secret/error redaction, unsafe deserialization or runtime eval, GenServer state and backpressure, serialization and event versioning, persistence backends, package boundaries, or observability. Triggers on "clean up codebase", "remediation pass", "audit OTP/supervision", "remove String.to_atom", "boundary/DTO cleanup", "redact secrets", "ban binary_to_term", "backpressure cleanup". Each invocation targets one pass and produces a focused PR with red-first tests, exit-gate scans, and documented exceptions.

xu-chris/ai-setup1 installsMITSynced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI
---
name: codebase-cleanup
description: Run focused, pass-based remediation on an Elixir/BEAM codebase to eliminate unsafe patterns and harden runtime boundaries. Use when auditing or cleaning up OTP supervision, DTO/boundary integrity, atom safety, ambient configuration, secret/error redaction, unsafe deserialization or runtime eval, GenServer state and backpressure, serialization and event versioning, persistence backends, package boundaries, or observability. Triggers on "clean up codebase", "remediation pass", "audit OTP/supervision", "remove String.to_atom", "boundary/DTO cleanup", "redact secrets", "ban binary_to_term", "backpressure cleanup". Each invocation targets one pass and produces a focused PR with red-first tests, exit-gate scans, and documented exceptions.
license: MIT
---

# Comprehensive Elixir Codebase Cleanup Guide

This guide is written for an **Elixir/BEAM-oriented codebase**, especially one with OTP processes, distributed execution, provider integrations, event/state persistence, and governed runtime boundaries. It consolidates the uploaded cleanup notes into a **pass-based remediation plan** so related concerns can be fixed together, reviewed together, and tested together.

---

## Cleanup Philosophy

Do **not** try to “clean the codebase” in one giant sweep.

Instead, run **focused passes**:

1. **Scan** for one class of issue.
2. **Classify** findings by risk and ownership.
3. **Fix** only that concern.
4. **Add red-first tests** proving the unsafe behavior fails.
5. **Run verification gates**.
6. **Document exceptions** explicitly.

Each pass should ideally produce one or more focused PRs with a narrow theme.

---

# Global Rules for Every Pass

## 1. No Regex for Structural Code Cleanup

Regex is acceptable only for **flat lexical validation** where the input grammar is intentionally regular.

Regex must **not** be used for:

* Elixir source rewrites
* `mix.exs` edits
* config transformations
* nested syntax parsing
* protocol payload parsing
* AST-sensitive changes
* multi-line source modifications

Use parser-aware tools instead:

```elixir
Code.string_to_quoted/2
Macro.prewalk/2
Macro.postwalk/2
```

Or dedicated source tooling such as:

```text
Sourceror
Rewrite
Igniter
```

**Allowed regex example:**

```elixir
Regex.match?(~r/^[a-z0-9_]+$/, identifier)
```

**Blocked regex example:**

```elixir
Regex.replace(~r/Application\.get_env\(.+\)/, source, replacement)
```

That can corrupt source code.

---

## 2. Every Pass Needs an Exit Gate

A pass is not complete because the code “looks cleaner.”

A pass is complete when:

* Relevant scans return zero unsafe findings, or only documented exceptions.
* Tests exist for rejected/invalid cases.
* Existing tests pass.
* New tests prove fail-closed behavior.
* The remediation is documented.
* No unrelated behavior was changed.

---

## 3. Exceptions Must Be Explicit

Use an exception annotation format like this:

```elixir
# cleanup:allow unsafe_deserialization
# reason: internal trusted migration payload generated by ReleaseTasks.V2 only
# owner: platform-runtime
# expires: 2026-08-01
```

Every exception should have:

| Field                     | Required |
| ------------------------- | -------: |
| Rule name                 |      Yes |
| Reason                    |      Yes |
| Owner                     |      Yes |
| Expiration or review date |      Yes |
| Test coverage             |      Yes |

---

# Recommended Cleanup Order

The order matters. Earlier passes reduce the risk of later passes.

| Order | Pass                                                | Why It Comes Here                                   |
| ----: | --------------------------------------------------- | --------------------------------------------------- |
|     0 | Baseline and Inventory                              | Establish current state before touching code        |
|     1 | Transformation Safety                               | Prevent cleanup tooling from corrupting code        |
|     2 | Boundary and DTO Integrity                          | Stop raw external input from leaking inward         |
|     3 | Atom Safety and Bounded Vocabulary                  | Remove atom exhaustion risks                        |
|     4 | Configuration and Ambient Authority                 | Remove hidden credentials/config access             |
|     5 | Secret Redaction and Error Sanitization             | Prevent leakage through logs/errors                 |
|     6 | Unsafe Deserialization and Runtime Eval             | Remove high-risk execution/data ingestion issues    |
|     7 | OTP Lifecycle and Supervision                       | Ensure all work is owned, killable, and restartable |
|     8 | Actor Mailbox, Backpressure, and Queue Bounds       | Prevent OOM and cascading node failure              |
|     9 | GenServer Functional-Core Cleanup                   | Remove hidden state and blocking logic              |
|    10 | Serialization, Protocol, and Versioning             | Stabilize external and durable contracts            |
|    11 | Persistence and State Backend Cleanup               | Isolate side effects and durable state              |
|    12 | Package and Dependency Boundaries                   | Remove transitive dependency coupling               |
|    13 | Observability and Context Propagation               | Make runtime behavior traceable                     |
|    14 | Idiomatic, Performance, and Maintainability Cleanup | Improve readability/performance after safety work   |
|    15 | Final Verification and Governance Lock-In           | Make the cleanup durable                            |

---

# Pass 0 — Baseline and Inventory

## Goal

Create a reliable picture of the codebase before changing it.

## Actions

Run the normal verification suite:

```bash
mix deps.get
mix compile
mix format --check-formatted
mix test
```

Where available, also run:

```bash
mix credo --strict
mix sobelow
mix deps.audit
```

Create a cleanup ledger:

```text
cleanup/
  findings.md
  exceptions.md
  pass-00-baseline.md
  pass-01-transformation-safety.md
  ...
```

Suggested finding format:

```markdown
## Finding

- Rule:
- File:
- Function/module:
- Risk:
- Owner:
- Fix strategy:
- Tests required:
- Status:
```

## Scan Inventory

Collect initial counts:

```bash
rg 'String\.to_atom|binary_to_term|Code\.eval|Application\.get_env|System\.get_env'
rg 'spawn\(|spawn_link\(|Task\.start|Task\.async|GenServer\.cast|Process\.send'
rg 'Logger\.|IO\.inspect|dbg\(|:telemetry\.execute'
rg 'defstruct|@derive|Jason\.Encoder|Poison\.Encoder'
rg 'Regex\.|~r/'
```

## Exit Criteria

* Baseline test status recorded.
* Cleanup ledger created.
* Initial risk areas categorized.
* No code changes except documentation/setup files.

---

# Pass 1 — Transformation Safety

## Goal

Make sure future cleanup work does not corrupt source code through unsafe automation.

## What to Fix

* Regex-based source rewrites
* Regex-based config rewrites
* brittle scripts that mutate Elixir files as plain text
* scripts that edit `mix.exs`, `config/*.exs`, or `.formatter.exs` without parsing
* agent-generated cleanup scripts that operate on syntax using string replacement

## Scans

```bash
rg 'Regex\.replace|String\.replace|File\.write!|File\.read!' scripts lib test priv
rg '~r/' scripts lib test priv
rg 'Code\.string_to_quoted|Sourceror|Igniter|Rewrite' scripts lib test priv
```

## Remediation

Replace unsafe transformations with AST-aware or schema-aware logic.

### Bad

```elixir
source
|> String.replace("Application.get_env(:app, :key)", "config.key")
```

### Better

```elixir
{:ok, ast} = Code.string_to_quoted(source)

new_ast =
  Macro.prewalk(ast, fn
    {{:., _, [{:__aliases__, _, [:Application]}, :get_env]}, _, args} ->
      rewrite_get_env(args)

    node ->
      node
  end)
```

## Tests to Add

* source rewrite preserves formatting-sensitive constructs
* comments are preserved where required
* nested expressions are not corrupted
* unrelated files remain unchanged
* malformed source fails safely

## Exit Criteria

* No source/config rewrite script relies on regex or blind string replacement.
* All cleanup automation has tests.
* Unsafe transformation tools are removed or quarantined.

---

# Pass 2 — Boundary and DTO Integrity

## Goal

Prevent raw, untrusted, or weakly typed input from entering core logic.

## What to Fix

* raw maps crossing domain boundaries
* unchecked GraphQL variables
* unchecked controller params
* unchecked queue payloads
* unchecked CLI inputs
* provider payloads passed directly into business logic
* functions accepting `%{}` where a typed struct is expected
* loosely shaped request/response objects

## Scans

```bash
rg 'def .*\(%\{|def .*\(params|def .*\(payload|def .*\(attrs|def .*\(input' lib
rg 'Map\.get|map\[:|payload\[|params\[|attrs\[' lib
rg '@enforce_keys|defstruct|typedstruct|Ecto\.Changeset' lib
```

## Remediation Pattern

At every external boundary:

```text
raw input → parser/validator → DTO/struct → domain logic
```

### Bad

```elixir
def execute(payload) do
  provider = payload["provider"]
  model = payload["model"]

  Provider.call(provider, model, payload)
end
```

### Better

```elixir
def execute(raw_payload) do
  with {:ok, request} <- InferenceRequest.new(raw_payload) do
    Provider.call(request)
  end
end
```

Example DTO:

```elixir
defmodule InferenceRequest do
  @enforce_keys [:provider_ref, :model_ref, :input]
  defstruct [:provider_ref, :model_ref, :input, opts: %{}]

  def new(%{"provider" => provider, "model" => model, "input" => input} = raw) do
    with {:ok, provider_ref} <- ProviderRef.new(provider),
         {:ok, model_ref} <- ModelRef.new(model),
         {:ok, validated_input} <- InputPayload.new(input) do
      {:ok,
       %__MODULE__{
         provider_ref: provider_ref,
         model_ref: model_ref,
         input: validated_input,
         opts: Map.get(raw, "opts", %{})
       }}
    end
  end

  def new(_), do: {:error, :invalid_inference_request}
end
```

## Tests to Add

Red-first tests for:

* missing required keys
* unknown provider refs
* invalid model refs
* malformed payloads
* extra fields if not allowed
* invalid enum values
* boundary rejects before core logic runs

## Exit Criteria

* Core modules no longer accept raw external maps.
* Boundary modules own parsing and validation.
* Unknown input fails closed.
* DTO constructors are tested.

---

# Pass 3 — Atom Safety and Bounded Vocabulary

## Goal

Eliminate unbounded atom creation and enforce finite vocabularies.

## What to Fix

* `String.to_atom/1` on user or external input
* `:"#{value}"` from runtime values
* `binary_to_atom/1`
* dynamic module names
* dynamic enum conversion
* unsafe GraphQL/provider/action names converted to atoms

## Scans

```bash
rg 'String\.to_atom|binary_to_atom|list_to_atom'
rg ':\#\{|:"\#\{'
rg 'String\.to_existing_atom'
```

## Remediation Options

### Use explicit registries

```elixir
defmodule ProviderRegistry do
  @providers %{
    "openai" => :openai,
    "anthropic" => :anthropic,
    "local" => :local
  }

  def parse(value), do: Map.fetch(@providers, value)
end
```

### Use strings as external identifiers

```elixir
%ProviderRef{id: "openai"}
```

### Use existing atoms only for known internal values

```elixir
String.to_existing_atom(value)
```

Only use this when all allowed atoms are loaded and controlled.

## Tests to Add

* unknown atom-like input fails
* large random strings do not create atoms
* valid known vocabulary succeeds
* provider/action/mode enums reject unknown values

## Exit Criteria

* No unbounded atom creation remains.
* All vocabularies are explicit.
* Dynamic identifiers remain strings or validated structs.
* Tests prove unknown values fail closed.

---

# Pass 4 — Configuration and Ambient Authority

## Goal

Remove hidden configuration and hidden authority from core logic.

## What to Fix

* `Application.get_env/2,3` in business logic
* `System.get_env/1,2` outside boot/config modules
* singleton clients
* default auth state
* implicit provider selection
* lower layers choosing credentials/endpoints
* tests mutating global config without restoring it

## Scans

```bash
rg 'Application\.get_env|Application\.fetch_env|System\.get_env'
rg 'put_env|delete_env'
rg 'defmodule .*Client|client\(|credentials|token|api_key|secret'
```

## Remediation Pattern

Resolve configuration once at boot or boundary, validate it, then inject it.

### Bad

```elixir
def call_provider(input) do
  api_key = System.get_env("OPENAI_API_KEY")
  model = Application.get_env(:my_app, :default_model)

  Provider.call(api_key, model, input)
end
```

### Better

```elixir
def call_provider(%ExecutionContext{} = context, input) do
  Provider.call(context.provider_grant, context.model_ref, input)
end
```

Example context:

```elixir
defmodule ExecutionContext do
  @enforce_keys [:authority, :provider_grant, :target]
  defstruct [:authority, :provider_grant, :target, :trace_context]
end
```

## Required Boundary Rule

Lower layers must not decide:

* which provider to use
* which credential to use
* which endpoint to call
* which tenant/target owns the request

They should only execute against explicit authority.

## Tests to Add

* missing authority fails
* malformed authority fails
* lower layer cannot call provider without explicit grant
* no global config is read in hot path
* config mutation in tests is restored in the same process

## Exit Criteria

* Runtime hot paths do not call `System.get_env`.
* Core logic does not call `Application.get_env`.
* Credentials are explicit handles/grants, not ambient values.
* Tests use deterministic config snapshots.

---

# Pass 5 — Secret Redaction and Error Sanitization

## Goal

Prevent sensitive data and internal topology from leaking through logs, traces, telemetry, exceptions, or serialized structs.

## What to Fix

* raw structs logged directly
* credentials in `Logger`
* bearer tokens in traces
* prompt fragments in telemetry
* raw provider responses in errors
* stacktraces returned to callers
* `IO.inspect`, `dbg`, or ad hoc debug output in production paths
* structs without safe `Inspect` implementations

## Scans

```bash
rg 'Logger\.|IO\.inspect|dbg\(|inspect\(' lib
rg ':telemetry\.execute' lib
rg '__STACKTRACE__|Exception\.format|reraise|rescue' lib
rg 'defstruct' lib
rg '@derive.*Inspect|defimpl Inspect' lib
```

## Remediation

### Derive safe inspection

```elixir
defmodule CredentialHandle do
  @derive {Inspect, only: [:id, :provider, :expires_at]}
  @enforce_keys [:id, :provider]
  defstruct [:id, :provider, :expires_at, :materialized_secret]
end
```

### Use structured redaction

```elixir
defmodule Redactor do
  @redacted "[REDACTED]"

  def credential(%CredentialHandle{} = handle) do
    %{
      id: handle.id,
      provider: handle.provider,
      secret: @redacted
    }
  end
end
```

### Sanitize edge errors

```elixir
def handle_error({:error, :provider_timeout}) do
  {:error, %{code: "provider_timeout", message: "Provider request timed out"}}
end

def handle_error(_unknown) do
  {:error, %{code: "internal_error", message: "Internal error"}}
end
```

## Tests to Add

* logs do not contain tokens
* telemetry metadata contains refs/hashes only
* inspecting credential structs does not expose secrets
* API errors do not expose stacktraces
* provider failures are mapped to bounded error codes

## Exit Criteria

* No raw secrets in logs/traces/errors.
* Sensitive structs have safe `Inspect`.
* External errors use sanitized DTOs.
* Stacktraces stay internal.

---

# Pass 6 — Unsafe Deserialization and Runtime Eval

## Goal

Remove high-risk dynamic execution and unsafe term ingestion.

## What to Fix

* `:erlang.binary_to_term/1`
* `binary_to_term/1`
* ETF deserialization from untrusted sources
* `Code.eval_string/1,2`
* `Code.eval_quoted/1,2,3`
* dynamic module/function invocation from external input
* provider/plugin systems using runtime eval

## Scans

```bash
rg 'binary_to_term|:erlang\.binary_to_term'
rg 'Code\.eval_string|Code\.eval_quoted|Code\.compile_string'
rg 'apply\(|Module\.concat|String\.to_atom'
```

## Remediation

### Bad

```elixir
term = :erlang.binary_to_term(payload)
```

### Safer if ETF is unavoidable

```elixir
term = :erlang.binary_to_term(payload, [:safe])
```

### Preferred for external payloads

Use JSON or another explicit format with DTO validation:

```elixir
with {:ok, decoded} <- Jason.decode(payload),
     {:ok, request} <- RequestDTO.new(decoded) do
  {:ok, request}
end
```

### Replace runtime eval

Do not execute strings.

Use:

* explicit command registries
* bounded DSL parsed into validated AST
* precompiled modules
* WASM sandboxing where truly necessary
* behaviour implementations selected from a registry

## Tests to Add

* unsafe ETF payloads are rejected
* arbitrary code strings are not executed
* unknown commands fail closed
* malformed plugin payloads fail closed

## Exit Criteria

* No unsafe term deserialization.
* No runtime code evaluation in hot paths.
* Any remaining deserialization has clear trust boundary documentation.
* Any dynamic dispatch uses a bounded registry.

---

# Pass 7 — OTP Lifecycle and Supervision Integrity

## Goal

Ensure every process is owned, supervised, observable, and killable.

## What to Fix

* bare `spawn`
* bare `spawn_link`
* `Task.start` without supervision
* `Task.async` without await/yield/shutdown
* hidden `GenServer.start_link` calls in business logic
* unowned background workers
* no shutdown policy
* no restart semantics
* dynamic supervisors used without ownership rules

## Scans

```bash
rg 'spawn\(|spawn_link\(|spawn_monitor\(' lib test
rg 'Task\.start|Task\.async|Task\.supervisor|Task\.await|Task\.yield' lib test
rg 'GenServer\.start|GenServer\.start_link|DynamicSupervisor\.start_child' lib test
rg 'Supervisor\.child_spec|use Supervisor|use DynamicSupervisor' lib
```

## Remediation

### Bad

```elixir
spawn(fn -> Provider.call(request) end)
```

### Better

```elixir
Task.Supervisor.start_child(MyApp.TaskSupervisor, fn ->
  Provider.call(request)
end)
```

### Better for durable/work queue processing

Use an explicit queue/worker system with supervision and backpressure.

## Required Design Decisions

For every process type, define:

| Question                          | Required Answer        |
| --------------------------------- | ---------------------- |
| Who owns it?                      | Supervisor/module      |
| Who can start it?                 | Public API             |
| Who can stop it?                  | Shutdown path          |
| What happens on crash?            | Restart strategy       |
| What state can it hold?           | Explicit state policy  |
| How is it observed?               | Telemetry/log metadata |
| How does it receive cancellation? | Timeout/shutdown rule  |

## Tests to Add

* worker crash does not crash unrelated domains
* supervised task exits cleanly
* task timeout triggers cancellation
* process restart preserves invariants
* no orphan process remains after request cancellation

## Exit Criteria

* No bare process spawning remains.
* All concurrent work is supervised.
* Restart strategies are deliberate.
* Shutdown timeouts are explicit.
* Process ownership is documented.

---

# Pass 8 — Actor Mailbox, Backpressure, and Queue Bounds

## Goal

Prevent unbounded mailbox growth, unbounded queues, and cascading memory failures.

## What to Fix

* high-frequency `GenServer.cast`
* unbounded `send`
* queue consumers without backpressure
* stream processors without flow control
* workers accepting unlimited messages
* no load shedding
* no circuit breakers
* no mailbox monitoring
* no idle hibernation for long-lived processes

## Scans

```bash
rg 'GenServer\.cast|handle_cast' lib
rg 'Process\.send|send\(' lib
rg 'handle_info' lib
rg ':queue|Queue|Broadway|GenStage' lib
rg ':hibernate' lib
```

## Remediation Options

Use the right primitive:

| Problem                     | Preferred Tool                  |
| --------------------------- | ------------------------------- |
| Bounded synchronous request | `GenServer.call` with timeout   |
| Stream ingestion            | `GenStage` / `Broadway`         |
| Background jobs             | Oban or supervised worker queue |
| Burst protection            | explicit queue limits           |
| Overload handling           | load shedding / circuit breaker |
| Idle process memory         | `:hibernate`                    |

### Bad

```elixir
Enum.each(events, fn event ->
  GenServer.cast(Consumer, {:event, event})
end)
```

### Better

```elixir
Producer.enqueue(events, max_demand: 100)
```

Or:

```elixir
case WorkerPool.checkout() do
  {:ok, worker} -> Worker.process(worker, event)
  {:error, :overloaded} -> {:error, :load_shed}
end
```

## Tests to Add

* queue rejects over capacity
* burst input does not grow mailbox indefinitely
* overloaded workers shed load
* slow consumer does not crash node
* timeout/circuit breaker trips as expected

## Exit Criteria

* High-volume async paths have backpressure.
* Mailboxes are bounded or monitored.
* Load shedding behavior is explicit.
* No critical path relies on unlimited `cast`.

---

# Pass 9 — GenServer Functional-Core Cleanup

## Goal

Separate pure business logic from process mechanics.

## What to Fix

* heavy computation inside `handle_call`
* network calls inside GenServer callbacks
* large mutable state bags
* process history affecting deterministic results
* business logic impossible to test without starting processes
* GenServers acting as caches, routers, workers, and state machines all at once

## Scans

```bash
rg 'use GenServer|handle_call|handle_cast|handle_info' lib
rg 'HTTPoison|Req\.|Finch|Tesla|Ecto|Repo\.' lib
rg 'Nx\.|EXLA|Bumblebee|Explorer' lib
```

## Remediation Pattern

```text
GenServer = process shell
Pure module = business logic
Adapter = side effects
```

### Bad

```elixir
def handle_call({:execute, request}, _from, state) do
  result = Provider.call(request)
  new_state = Map.put(state, request.id, result)

  {:reply, result, new_state}
end
```

### Better

```elixir
def handle_call({:execute, request}, _from, state) do
  with {:ok, command} <- RuntimeLogic.prepare(request, state),
       {:ok, task_ref} <- RuntimeTasks.start(command) do
    {:reply, {:ok, task_ref}, state}
  end
end
```

Pure logic:

```elixir
defmodule RuntimeLogic do
  def prepare(%Request{} = request, state) do
    # deterministic transformation only
    {:ok, %Command{request: request, prior_state: state}}
  end
end
```

## Tests to Add

* pure logic can be tested without a process
* same input/state produces same output
* process restart does not change behavior
* slow side effects do not block the GenServer
* state transitions are explicit

## Exit Criteria

* GenServers coordinate; they do not contain core business rules.
* Blocking side effects are moved out.
* Pure logic has direct unit tests.
* Process state is minimal and justified.

---

# Pass 10 — Serialization, Protocol, and Versioning

## Goal

Stabilize external and durable interfaces.

## What to Fix

* raw struct encoding
* inconsistent JSON field names
* inconsistent enum formats
* unversioned event payloads
* unversioned API responses
* direct internal struct exposure
* GraphQL/HTTP schemas drifting from domain types
* Python/Elixir bridge payloads without schema/version
* durable state changes without upcasters

## Scans

```bash
rg 'Jason\.encode|Jason\.Encoder|Poison\.Encoder|derive.*Jason' lib
rg 'defimpl Jason\.Encoder|defimpl Poison\.Encoder' lib
rg 'version|schema_version|event_version' lib priv test
rg 'GraphQL|Absinthe|Plug|Phoenix\.Controller' lib
```

## Remediation Pattern

Internal structs should not automatically become external JSON.

Use explicit mappers:

```elixir
defmodule ProviderRefJSON do
  def encode(%ProviderRef{} = ref) do
    %{
      "version" => "v1",
      "id" => ref.id,
      "family" => to_string(ref.family)
    }
  end
end
```

Version durable events:

```elixir
%{
  "event_type" => "provider_selected",
  "version" => 1,
  "data" => %{
    "provider_id" => "openai"
  }
}
```

Add upcasters:

```elixir
defmodule EventUpcaster do
  def upcast(%{"event_type" => "provider_selected", "version" => 1} = event) do
    put_in(event, ["version"], 2)
    |> put_in(["data", "provider_ref"], event["data"]["provider_id"])
  end

  def upcast(%{"version" => 2} = event), do: event
end
```

## Tests to Add

* JSON snapshot tests
* GraphQL response snapshot tests
* old event versions upcast correctly
* unknown event versions fail closed
* Python/Elixir bridge roundtrips typed envelopes
* enum/string representation is stable

## Exit Criteria

* External contracts are explicit.
* Durable payloads are versioned.
* No raw internal struct leaks through JSON.
* Historical state can be read safely.

---

# Pass 11 — Persistence and State Backend Cleanup

## Goal

Isolate durable state and side effects behind explicit interfaces.

## What to Fix

* direct `Repo` calls scattered through core logic
* persistence mixed with business decisions
* no in-memory backend for tests
* unclear durability tier
* state mutation hidden in helper modules
* event sourcing payloads without migration path
* cache/state ambiguity

## Scans

```bash
rg 'Repo\.|Ecto\.Multi|insert!|update!|delete!|transaction' lib
rg 'ETS|:ets|Cache|Agent|persistent_term' lib
rg 'EventStore|append|projection|snapshot' lib
```

## Remediation Pattern

Create backend behaviours.

```elixir
defmodule StateBackend do
  @callback put(key :: term(), value :: term()) :: :ok | {:error, term()}
  @callback get(key :: term()) :: {:ok, term()} | {:error, :not_found | term()}
end
```

Implement backends:

```text
InMemoryBackend
ETSBackend
PostgresBackend
EventLogBackend
```

Inject the backend:

```elixir
def execute(%ExecutionContext{state_backend: backend}, command) do
  backend.put(command.key, command.value)
end
```

## Define Durability Tiers

| Tier   | Meaning                         |
| ------ | ------------------------------- |
| Tier 0 | Ephemeral, memory-only          |
| Tier 1 | Recoverable within process/node |
| Tier 2 | Durable across deploy/restart   |
| Tier 3 | Auditable event-sourced history |

## Tests to Add

* core logic runs with in-memory backend
* DB adapter is tested separately
* failed persistence does not partially mutate domain state
* event version migration works
* replay produces deterministic state

## Exit Criteria

* Core logic does not directly call the database.
* Persistence is adapter-owned.
* Tests can run without external services.
* Durability level is explicit.

---

# Pass 12 — Package and Dependency Boundaries

## Goal

Prevent packages/apps from relying on accidental transitive dependencies or root-only configuration.

## What to Fix

* child apps using deps not declared in their own `mix.exs`
* umbrella apps reading root config directly
* shared utility modules with unclear ownership
* circular package dependencies
* “misc” or “mezzanine dump” modules
* test-only dependencies used in production modules

## Scans

```bash
find . -name mix.exs -maxdepth 4
rg 'defp deps|app:|in_umbrella' .
rg 'alias .*\.|import .*\.|require .*' apps lib test
rg 'Application\.get_env' apps lib
```

## Remediation

For every package/app:

* declare its own dependencies
* define its own typed config module
* expose a clear public API
* avoid importing sibling internals
* move shared contracts into explicit interface packages
* remove root-only assumptions

## Boundary Questions

For each module:

| Question                       | Required Answer     |
| ------------------------------ | ------------------- |
| Which package owns this?       | One package         |
| Is it public API?              | Yes/no              |
| Who imports it?                | Known consumers     |
| Can it move?                   | Extraction criteria |
| Does it depend on root config? | Should be no        |

## Tests to Add

* each child app compiles independently where practical
* no implicit dependency usage
* config modules validate locally
* package public API tests exist

## Exit Criteria

* No transitive dependency reliance.
* Config ownership is local and typed.
* Package boundaries are documented.
* Shared modules have explicit ownership.

---

# Pass 13 — Observability and Context Propagation

## Goal

Ensure runtime behavior can be traced across process, queue, node, and provider boundaries.

## What to Fix

* dropped trace IDs
* dropped tenant/target IDs
* telemetry without bounded metadata
* async tasks losing context
* logs without correlation IDs
* provider calls lacking request IDs
* background jobs disconnected from originating request

## Scans

```bash
rg ':telemetry\.execute|Logger\.metadata|Logger\.info|Logger\.error' lib
rg 'trace_id|request_id|tenant_id|correlation_id|span' lib
rg 'Task\.|GenServer\.cast|Broadway|Oban|DynamicSupervisor' lib
```

## Remediation Pattern

Use an explicit trace/context object.

```elixir
defmodule TraceContext do
  @enforce_keys [:trace_id]
  defstruct [:trace_id, :tenant_ref, :target_ref, :operation]
end
```

Pass it across async boundaries:

```elixir
Task.Supervisor.start_child(MyApp.TaskSupervisor, fn ->
  Logger.metadata(trace_id: context.trace.trace_id)
  Worker.run(context, command)
end)
```

Telemetry should use bounded metadata:

```elixir
:telemetry.execute(
  [:my_app, :provider, :request, :stop],
  %{duration_ms: duration},
  %{
    trace_id: context.trace.trace_id,
    provider_ref: context.provider.ref,
    result: :ok
  }
)
```

Never include:

* raw prompt text
* bearer tokens
* credential material
* full provider responses
* raw user payloads

## Tests to Add

* async task receives trace context
* telemetry metadata is bounded
* sensitive fields are redacted
* provider request logs include correlation ID
* queue handoff preserves context

## Exit Criteria

* Cross-boundary operations have trace continuity.
* Telemetry is structured and safe.
* Sensitive values are never emitted.
* Async boundaries preserve context.

---

# Pass 14 — Idiomatic, Performance, and Maintainability Cleanup

## Goal

Improve readability and runtime behavior after safety-critical issues are under control.

This pass should come late because it often touches many files and can create noise.

---

## 14A. Regex Replacement for Structural Parsing

## What to Fix

* regex used to parse protocols
* regex used to parse nested text
* regex used repeatedly in hot paths
* regex used where binary matching or parser combinators are clearer

## Scans

```bash
rg 'Regex\.|~r/' lib test
```

## Remediation Options

### Use binary pattern matching

```elixir
def parse_auth_header(<<"Bearer ", token::binary>>) when byte_size(token) > 0 do
  {:ok, token}
end

def parse_auth_header(_), do: {:error, :invalid_auth_header}
```

### Use `String.split/3` for simple delimiters

```elixir
case String.split(value, ":", parts: 2) do
  [left, right] -> {:ok, {left, right}}
  _ -> {:error, :invalid_value}
end
```

### Use parser combinators for real grammars

Use `NimbleParsec` or a purpose-built parser when the input has grammar.

## Exit Criteria

* Regex remains only where truly appropriate.
* Hot-path regex is removed or precompiled.
* Structural parsing uses parsers or binary matching.

---

## 14B. Nested `case` Cleanup

## What to Fix

Deeply nested success/error flows.

### Bad

```elixir
case parse(input) do
  {:ok, parsed} ->
    case validate(parsed) do
      {:ok, valid} ->
        execute(valid)

      error ->
        error
    end

  error ->
    error
end
```

### Better

```elixir
with {:ok, parsed} <- parse(input),
     {:ok, valid} <- validate(parsed) do
  execute(valid)
end
```

## Exit Criteria

* Sequential failable flows use `with`.
* Error cases remain explicit.
* Complex fallback logic is not obscured.

---

## 14C. Pattern Matching in Function Heads

## What to Fix

* large `if`/`cond` branches dispatching on shape/type
* manual key checks
* generic functions with many internal branches

### Bad

```elixir
def handle(event) do
  if event.type == :created do
    ...
  else
    ...
  end
end
```

### Better

```elixir
def handle(%Event{type: :created} = event), do: ...
def handle(%Event{type: :deleted} = event), do: ...
def handle(%Event{}), do: {:error, :unsupported_event}
```

## Exit Criteria

* Branching by shape is moved into function heads.
* Invalid shapes fail clearly.
* Guards are used where appropriate.

---

## 14D. Ecto Query Composition and N+1 Cleanup

## What to Fix

* huge inline queries
* queries scattered across controllers
* `Repo.get` or `Repo.all` inside loops
* missing preloads
* business logic inside query modules
* query fragments duplicated across files

## Scans

```bash
rg 'Repo\.all|Repo\.get|Repo\.one|Repo\.preload' lib
rg 'Enum\.map.*Repo\.|for .* <- .* do.*Repo\.' lib
rg 'from\(.*in' lib
```

## Remediation

Compose queries:

```elixir
def active(query), do: where(query, [x], x.status == :active)

def ordered(query), do: order_by(query, [x], desc: x.inserted_at)

User
|> active()
|> ordered()
|> Repo.all()
```

Avoid N+1:

```elixir
users =
  User
  |> preload([:profile, :roles])
  |> Repo.all()
```

## Exit Criteria

* Reusable query functions exist.
* Controllers/LiveViews do not own query complexity.
* N+1 paths are eliminated or documented.

---

# Pass 15 — Final Verification and Governance Lock-In

## Goal

Make the cleanup durable so the codebase does not regress.

## Final Verification Suite

Run:

```bash
mix format --check-formatted
mix compile --warnings-as-errors
mix test
mix credo --strict
```

Where applicable:

```bash
mix sobelow
mix deps.audit
```

Run targeted scans again:

```bash
rg 'String\.to_atom|binary_to_atom|list_to_atom'
rg 'binary_to_term|:erlang\.binary_to_term'
rg 'Code\.eval_string|Code\.eval_quoted|Code\.compile_string'
rg 'Application\.get_env|System\.get_env'
rg 'spawn\(|spawn_link\(|Task\.start|Task\.async'
rg 'GenServer\.cast|Process\.send'
rg 'Logger\.|IO\.inspect|dbg\('
rg 'Regex\.|~r/'
```

## Add CI Gates

Add automated checks for the most important rules.

Example script:

```bash
#!/usr/bin/env bash
set -euo pipefail

fail_if_found() {
  pattern="$1"
  message="$2"

  if rg "$pattern" lib apps test; then
    echo "$message"
    exit 1
  fi
}

fail_if_found 'String\.to_atom' 'Unsafe dynamic atom creation found'
fail_if_found 'binary_to_term\([^,]+\)' 'Unsafe binary_to_term without [:safe] found'
fail_if_found 'Code\.eval_string|Code\.eval_quoted' 'Runtime code evaluation found'
fail_if_found 'System\.get_env' 'System.get_env found outside approved config layer'
fail_if_found 'spawn\(|spawn_link\(' 'Bare process spawning found'
```

Keep exceptions in a reviewed allowlist rather than silently ignoring them.

---

# Per-Pass Review Checklist

Use this checklist for every cleanup PR.

## Scope

* [ ] PR addresses only one cleanup pass.
* [ ] No unrelated formatting churn.
* [ ] No broad rewrite without tests.
* [ ] No regex-based source transformation.

## Safety

* [ ] Unsafe behavior has a red-first test.
* [ ] Unknown inputs fail closed.
* [ ] Secrets are not logged.
* [ ] Errors are sanitized at boundaries.
* [ ] No hidden config or authority was introduced.

## Runtime

* [ ] New processes are supervised.
* [ ] Async work has cancellation/timeout behavior.
* [ ] Queues/mailboxes are bounded or monitored.
* [ ] Side effects are isolated behind adapters.

## Contracts

* [ ] External payloads use DTOs.
* [ ] Serialized outputs are explicit and versioned.
* [ ] Durable events/states have migration/upcasting behavior.
* [ ] Cross-language payloads have stable envelopes.

## Verification

* [ ] Unit tests pass.
* [ ] Integration tests pass.
* [ ] Property/snapshot tests added where appropriate.
* [ ] Credo/Sobelow pass where configured.
* [ ] Cleanup ledger updated.

---

# Suggested Cleanup Ledger Structure

```markdown
# Cleanup Ledger

## Pass 0 — Baseline

Status:
Owner:
Started:
Completed:

### Findings

| ID | Rule | File | Risk | Owner | Status |
|---|---|---|---|---|---|

### Exceptions

| Rule | File | Reason | Owner | Expires |
|---|---|---|---|---|

### Tests Added

-

### Verification

- [ ] mix format --check-formatted
- [ ] mix compile --warnings-as-errors
- [ ] mix test
- [ ] mix credo --strict
```

---

# Risk Ranking

Use this to prioritize findings inside each pass.

| Severity | Meaning                                                                     | Examples                                                         |
| -------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------- |
| Critical | Can leak secrets, execute code, crash node, or violate authority boundaries | `Code.eval_string`, unsafe `binary_to_term`, raw secrets in logs |
| High     | Can cause fail-open behavior, data corruption, or unbounded resource growth | unbounded atoms, unsupervised tasks, unbounded mailbox           |
| Medium   | Can cause drift, nondeterminism, or maintenance hazards                     | raw maps, unversioned events, scattered config                   |
| Low      | Readability or local maintainability issue                                  | nested `case`, query composition, minor regex usage              |

---

# Recommended Pass Groupings for PRs

For a large codebase, use this PR sequence:

## PR Group 1 — Safety Rails

* Pass 0: Baseline and inventory
* Pass 1: Transformation safety
* Add cleanup ledger and CI scan skeleton

## PR Group 2 — Input and Authority

* Pass 2: Boundary and DTO integrity
* Pass 3: Atom safety
* Pass 4: Configuration and ambient authority

## PR Group 3 — Leakage and Execution Safety

* Pass 5: Secret redaction and error sanitization
* Pass 6: Unsafe deserialization and runtime eval

## PR Group 4 — OTP Runtime Integrity

* Pass 7: Supervision
* Pass 8: Mailbox/backpressure
* Pass 9: GenServer functional-core cleanup

## PR Group 5 — Contracts and State

* Pass 10: Serialization/protocol/versioning
* Pass 11: Persistence/state backend cleanup
* Pass 12: Package/dependency boundaries

## PR Group 6 — Observability and Polish

* Pass 13: Observability/context propagation
* Pass 14: Idiomatic/performance cleanup
* Pass 15: Final governance lock-in

---

# Final Cleanup Definition of Done

The cleanup is complete when:

* External input is validated at the edge.
* Core logic receives typed structs, not raw maps.
* Unknown values fail closed.
* Dynamic atom creation is removed.
* Runtime config and credentials are explicitly injected.
* Secrets do not appear in logs, traces, telemetry, errors, or inspections.
* Unsafe deserialization and runtime eval are eliminated.
* Every process is supervised.
* High-volume async paths have backpressure.
* GenServers coordinate rather than contain business logic.
* Durable/external payloads are versioned.
* State and persistence are behind explicit backends.
* Package boundaries are explicit.
* Trace context survives async and distributed boundaries.
* Regex is not used for structural parsing or source transformation.
* CI prevents regression.

More Observability skills

google-agents-cli-observability

google/agents-cli

>

106.1k

azure-observability

microsoft/azure-skills

Azure Observability Services including Azure Monitor, Application Insights, Log Analytics, Alerts, and Workbooks. Provides metrics, APM, distributed tracing, KQL queries, and interactive reports. USE FOR: Azure Monitor, Application Insights, Log Analytics, Alerts, Workbooks, metrics, APM, distributed tracing, KQL queries, interactive reports, observability, monitoring dashboards. DO NOT USE FOR: instrumenting apps with App Insights SDK (use appinsights-instrumentation), querying Kusto/ADX clusters (use azure-kusto), cost analysis (use azure-cost-optimization).

98.1k

social

coreyhaines31/marketingskills

When the user wants help creating, scheduling, or optimizing social media content for LinkedIn, Twitter/X, Instagram, TikTok, Facebook, or other platforms, or wants to do social listening and engagement triage. Also use when the user mentions 'LinkedIn post,' 'Twitter thread,' 'social media,' 'content calendar,' 'social scheduling,' 'engagement,' 'viral content,' 'what should I post,' 'repurpose this content,' 'tweet ideas,' 'LinkedIn carousel,' 'social media strategy,' 'grow my following,' 'TikTok video,' 'Reels,' 'Shorts,' 'video script,' 'video hook,' 'short-form video,' 'create a reel,' 'social listening,' 'brand mentions,' 'competitor monitoring,' 'top posts to comment on,' 'find people asking for,' 'carousel,' 'slide-by-slide,' or 'document post.' Use this for social media content creation, repurposing, scheduling, short-form video scripting, and social listening. For broader content strategy, see content-strategy. For paid ads, see ad-creative. For earned media, see public-relations.

55.7k

← All Observability skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY