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_atpreserved (COALESCE);updated_atrefreshed. - 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)