Skip to content

Fix Ecto.ConstraintError on audit_logs_pkey during *_and_log (OPS-4610)#71

Closed
palantir-valiot[bot] wants to merge 1 commit into
mainfrom
palantir/OPS-4610-fix-audit-logs-pkey-constraint-error
Closed

Fix Ecto.ConstraintError on audit_logs_pkey during *_and_log (OPS-4610)#71
palantir-valiot[bot] wants to merge 1 commit into
mainfrom
palantir/OPS-4610-fix-audit-logs-pkey-constraint-error

Conversation

@palantir-valiot

Copy link
Copy Markdown

Description

Declare unique_constraint(:id, name: "<table>_pkey") (e.g. audit_log_pkey) inside the internal changelog_changeset/1 builder used by all *_and_log/* and log/* paths.

Before: a duplicate insert attempt for an audit log row (caused by retries, concurrent Multi steps, log-and-log races inside a user transaction, or external retry of a GraphQL mutation) would hit repo.insert/1 with a changeset that had never called unique_constraint, producing an unhandled Ecto.ConstraintError on audit_logs_pkey exactly as shown in the stacktrace:

(ecto_trail 1.0.3) lib/ecto_trail/ecto_trail.ex:435: EctoTrail.log_changes/5
...
Ecto.ConstraintError ... "audit_logs_pkey" (unique_constraint)
The changeset has not defined any constraint.

After: the constraint is declared on the changeset, so Postgres unique violation is translated by Ecto into {:error, %Ecto.Changeset{valid?: false, errors: [id: {"has already been taken", ...}]}}. The call sites already treat {:error, _} from the log step as non-fatal to the main operation (they log and return {:ok, reason}), so the user's update_and_log / transaction succeeds and only the duplicate audit row is suppressed. No behavior change for the happy path.

Fixes the root cause identified in triage for OPS-4610.

Why

  • Triage decision: NOTIFY+FIX (severity high, category code_bug).
  • The bug is a clear omission in the first-party Valiot package valiot/ecto_trail: the Changelog insert path never registered the pkey unique constraint that the table actually has (see migration and @table_name config).
  • Matches the exact frame in the prod stacktrace from eliot-lamosa-gto-prod during a mutation{updateDevice...} that called update_and_log inside Repo.transaction.
  • Protocol requires a fix for any identifiable code bug.

Closes OPS-4610

Test plan

  • TDD (per AGENTS.md): added the regression tests first (new describe "audit log pkey unique constraint handling (OPS-4610)" block in test/unit/ecto_trail_test.exs).
    • First test primes a colliding id row directly into audit_log, builds a duplicate via the exact same changelog_changeset helper now exposed as __test_changelog_changeset__/1 for the test env, and asserts TestRepo.insert(cs) returns {:error, %Ecto.Changeset{valid?: false}} instead of raising.
    • Second test does a real update_and_log after priming a pkey collision and asserts the main update succeeds (the log failure is swallowed, which is the pre-existing and desired behavior for log-only errors).
  • DB-free verification under MIX_ENV=test: directly inspected the emitted cs.constraints and confirmed {:constraint_check, true, [%{type: :unique, constraint: "audit_log_pkey", field: :id, ...}]} — this is the signal that would have been missing before the fix.
  • mix format (clean, exit 0).
  • mix compile (clean; only pre-existing redundant-clause warning unrelated to this change).
  • git diff (post-commit) reviewed for scope, debug prints, half-done work, empty diffs — none present. Only the minimal fix + TDD tests + version bump + one-line changelog entry.
  • Version bump: mix.exs 1.0.3 → 1.0.4 (semver patch for bugfix).
  • CHANGELOG.md updated with a single scannable line per baseline rules.
  • Pushed exclusively via git push-safe (never plain git push).
  • This PR body was written from the skeleton in pull_request_template.md; every section filled with actual rationale, file paths, and test details.
  • Note: full mix test could not execute in the agent pod (Postgres not reachable on localhost:5432; connection refused). The constraint behavior and changeset construction were exercised directly without the DB. CI on the PR will run the complete suite including the new TDD cases against a real database.

Files changed (from git diff --stat)

CHANGELOG.md                  |  6 +++
lib/ecto_trail/ecto_trail.ex  | 12 +++++-
mix.exs                       |  2 +-
test/unit/ecto_trail_test.exs | 89 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 107 insertions(+), 2 deletions(-)

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

See "Test plan" above. The new tests in test/unit/ecto_trail_test.exs would have reproduced the original Ecto.ConstraintError before the one-line change to changelog_changeset/1.

Test Configuration (agent pod):

  • Elixir: (via mise)
  • Ecto 3.14.0 / ecto_sql 3.14.0
  • No runtime Postgres for this pod (CI provides it)

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation (CHANGELOG.md)
  • My changes generate no new warnings (pre-existing warning only)
  • I have added tests that prove my fix is effective (TDD, see above)
  • Pushed via mandated git push-safe
  • PR body written from pull_request_template.md skeleton with all sections filled

Declare unique_constraint(:id, name: "<table>_pkey") in changelog_changeset/1.
Duplicate log attempts (retries, concurrent Multi steps, etc.) now return
{:error, changeset} instead of raising.

TDD: added regression tests under "audit log pkey unique constraint handling".
Bumped to 1.0.4, updated CHANGELOG.

Closes OPS-4610
@linear-code

linear-code Bot commented Jun 13, 2026

Copy link
Copy Markdown

OPS-4610

@acrogenesis

Copy link
Copy Markdown
Member

Closing as a duplicate of #24 — same bug: Ecto.ConstraintError on audit_logs_pkey in EctoTrail.log_changes/5. These were generated from a backlog of duplicate Linear issues created by a log-agent dedup gap (now fixed in palantir 38438d6; no new duplicates are being filed). Consolidating on #24.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant