Record ci_config_first_generated_at when tracking AI-generated config

What does this MR do and why?

Part of instrumenting the AI Pipeline Builder / CI Expert Agent activation funnel (#594029). It lays the groundwork for the upcoming Pipeline Results Reviewed step by recording when a project's CI config was first generated by the agent, the timestamp that step uses to tell genuine agent pipelines apart from ones that predate the agent.

Concretely: Ci::ProjectMetric.track_ai_generated_config! already records a project's CI config origin (ci_config_generated_by, last-agent-wins). This MR extends the same upsert to also stamp ci_config_first_generated_at on the first agent commit (first-wins via COALESCE), while preserving updated_at. A later MR gates the view_ai_pipeline_results event on this timestamp so pipelines predating the agent don't count as reviews.

Behaviour

  • First agent commit: both columns set, ci_config_first_generated_at = now.
  • Re-commit: ci_config_generated_by = latest agent; ci_config_first_generated_at preserved (COALESCE); updated_at refreshed.
  • Existing row with NULL ci_config_first_generated_at: backfilled on the next agent commit; rows that never see another agent commit stay NULL and are not counted downstream.

Database

Upsert change: custom on_duplicate (ci_config_generated_by last-wins, ci_config_first_generated_at first-wins via COALESCE, updated_at refreshed; created_at/updated_at auto-populated by Rails on insert). Plan is local: the column ships in !240017 (merged), so the Database Lab clone cannot plan it yet.

INSERT INTO ci_project_metrics
  (project_id, ci_config_generated_by, ci_config_first_generated_at, created_at, updated_at)
VALUES (1, 'ci_expert_agent/v1', '2026-06-10 15:11:20', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
ON CONFLICT (project_id) DO UPDATE SET
  ci_config_generated_by = EXCLUDED.ci_config_generated_by,
  ci_config_first_generated_at = COALESCE(
    ci_project_metrics.ci_config_first_generated_at,
    EXCLUDED.ci_config_first_generated_at),
  updated_at = EXCLUDED.updated_at
RETURNING id;
Insert on ci_project_metrics  (cost=0.00..0.02 rows=1 width=104)
  Conflict Resolution: UPDATE
  Conflict Arbiter Indexes: index_ci_project_metrics_on_project_id
  ->  Result  (cost=0.00..0.02 rows=1 width=104)

How to validate locally

In a rails console, confirm first-wins / last-wins / updated_at refresh across two separate-transaction commits (the updated_at refresh can't be covered by a spec, since Postgres holds now() constant within the test transaction):

p = Project.first
Ci::ProjectMetric.track_ai_generated_config!(p.id, author_source: 'ci_expert_agent/v1')
row = Ci::ProjectMetric.find_by(project_id: p.id)
[row.ci_config_generated_by, row.ci_config_first_generated_at, row.updated_at]

# re-commit (separate transaction, so now() advances)
Ci::ProjectMetric.track_ai_generated_config!(p.id, author_source: 'ci_expert_agent/v1')
row.reload
# expect: ci_config_first_generated_at unchanged (first-wins), updated_at advanced (refresh)
[row.ci_config_first_generated_at, row.updated_at]

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Depends on: Add AI config activation timestamps to `ci_proj... (!240017 - merged) (migration; this MR targets its branch). Related: CI Expert Agent — Success Metrics (Simplified, GA) (#594029)

Edited by Sahil Sharma

Merge request reports

Loading