Skip to content

fix(controlplane): emit an audit event when a project version is promoted to latest - #3317

Merged
javirln merged 2 commits into
chainloop-dev:mainfrom
javirln:javier/pfm-6843-products-tracking-latest-fall-behind-promoting-a-project
Jul 31, 2026
Merged

fix(controlplane): emit an audit event when a project version is promoted to latest#3317
javirln merged 2 commits into
chainloop-dev:mainfrom
javirln:javier/pfm-6843-products-tracking-latest-fall-behind-promoting-a-project

Conversation

@javirln

@javirln javirln commented Jul 31, 2026

Copy link
Copy Markdown
Member

Problem

A project version becomes the latest one for its project in two ways: it is created as latest, or an already existing version is promoted. Only the first emitted an audit event.

When an attestation promoted an existing version, the promote branch left the creation result unchanged and nothing was dispatched. Consumers that reconcile off these events never learned the latest pointer had moved, so anything following the latest version of a project kept pointing at the previous one indefinitely. Nothing re-checks afterwards, so the state never healed.

Changes

  • WorkflowRunRepo.Create reports the promotion back through a new VersionPromoted flag on its result.
  • WorkflowRunUseCase.Create dispatches ProjectVersionUpdated when a promotion happened, alongside the existing ProjectVersionCreated for newly created versions.
  • ProjectVersionUseCase.MarkAsLatest gained the auditor dependency and dispatches the same event. It previously had none, so that path was silent too.
  • ProjectVersionUpdated carries a new marked_as_latest field, always emitted, so consumers can distinguish a promotion from a rename. Its description reflects the promotion.
  • The promotion contract, meaning the row-lock requirement, the released-version validation and whether the pointer actually moved, now lives in the single helper both paths already call instead of being recomputed at each call site.
  • A stale comment claiming the platform builds its mark-latest endpoint on top of ProjectVersionUseCase.MarkAsLatest is corrected. The platform implements its own and does not reference this use case.

A promotion that does not move the latest pointer emits nothing, so re-attesting the version that already is the latest stays silent.

Notes for reviewers

Promotions deliberately reuse the ProjectVersionUpdated action type rather than introducing a dedicated one. Subjects are published as audit.<target_type>.<action_type> and consumers subscribe to a fixed list of them, so a new action type would land on a subject nobody is listening to and every promotion would be dropped, which is the failure this event exists to prevent. If the action type ever changes, the consumers must subscribe to the new subject and be released first. This is documented at the dispatch site.

The ProjectVersionUseCase methods touched here have no callers today, in this repository or in the platform. The change to MarkAsLatest is regression-proofing for the biz-layer entry point, not a fix to a live path; the attestation path is the reachable one.

Known limitation, pre-existing and not addressed here: concurrent promotions of two different versions of the same project are not serialised against each other, since the row lock covers only the version being promoted. Closing that needs a lock on the project row or a unique partial index on (project_id) WHERE latest.

AI disclosure

This contribution was produced with the assistance of Claude Code. Every commit carries an Assisted-by: Claude Code trailer.

…oted to latest

A project version becomes the latest one for its project in two ways: it is
created as latest, or an already existing version is promoted. Only the first
was reported.

When an attestation promoted an existing version, the promote branch left the
creation result unchanged and nothing was dispatched. Consumers that reconcile
off these events never learned the pointer had moved, and kept following the
previously latest version indefinitely, with no path to recovery since nothing
re-checks afterwards.

Report the promotion back from the repository and dispatch
ProjectVersionUpdated carrying a new marked_as_latest field, both from the
attestation path and from ProjectVersionUseCase.MarkAsLatest, which had no
auditor at all. A promotion that does not move the latest pointer stays silent
so the audit log is not filled with no-ops.

The promotion contract, meaning the row-lock requirement, the released-version
validation and whether the pointer actually moved, now lives in the single
helper both paths already call rather than being recomputed at each call site.

Assisted-by: Claude Code
Signed-off-by: Javier Rodriguez <javier@chainloop.dev>

Chainloop-Trace-Sessions: 82261846-ded9-4e52-8707-d8a769402b5a
@chainloop-platform

chainloop-platform Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

AI Session Checks — 🔴 25% · ⚠️ 1 failing

Avg score Sessions Failing policies Attribution Files Lines Total Duration
🔴 25% 1 ⚠️ 1 93% AI / 7% Human 15 +594 / -46 15h42m11s

🔴 25% — 93% AI — ⚠️ 1 policies failing

Jul 30, 2026 15:42 UTC · 15h42m11s · $104.33 · 18.4k in / 681.7k out · claude-code 2.1.220 (claude-opus-5)

View session details ↗

Change Summary

  • Emits ProjectVersionUpdated when promotions move a version to latest, from both attestation and MarkAsLatest paths.
  • Extends the audit payload and descriptions with marked_as_latest, plus new goldens and audit tests.
  • Refactors promotion handling into shared helpers and adds broader unit, integration, and end-to-end verification.

AI Session Overall Score

🔴 25% — Strong verification, but repeated misalignment and user corrections make this reviewer-check PR.

AI Session Analysis Breakdown

🟢 92% · verification

🟢 New tests were watched failing first, then passing after the fix. · High Impact

🟢 The final pass reran unit, integration, and fresh-stack UI-backed E2E. · High Impact

🟢 84% · solution-quality

🟢 The root cause was isolated with a controlled binary A/B experiment. · High Impact

🟡 72% · scope-discipline

🟠 The code also changed the unreachable MarkAsLatest path as regression-proofing beyond the reported bug. · Medium Severity

💡 Surface prophylactic sibling-path fixes for approval before bundling them into a customer bug fix.

🔴 35% · context-and-planning

🔴 A multi-repo code, lab, UI, and PR session ran without a visible plan until late workflow prompts. · High Severity

💡 For multi-axis tasks, write a short shared plan before editing, reproducing, or filing follow-up work.

🔴 32% · alignment

🔴 AI opened new workflow branches before checking core premises, including an issue later admitted wrong and cancelled. · High Severity

💡 Before opening PRs or follow-up issues, verify the premise and ask for approval at each workflow boundary.

🔴 28% · user-trust-signal

🔴 User corrections clustered around process and verification, including 'don't open the PR' and 'NO ASSUMPTIONS'. · High Severity

💡 When corrections start clustering, stop and force a restated plan instead of continuing with incremental redirects.


File Attribution

██████████████████░░ 93% AI / 7% Human

Status Attribution File Lines
created ai app/controlplane/pkg/biz/workflowrun_audit_test.go +100 / -0
created ai app/controlplane/pkg/biz/auditor_test.go +82 / -0
created ai app/controlplane/pkg/biz/projectversion_audit_test.go +80 / -0
modified ai app/controlplane/pkg/biz/projectversion.go +69 / -6
modified ai app/controlplane/pkg/data/projectversion.go +63 / -12
modified ai app/controlplane/pkg/biz/workflowrun_integration_test.go +54 / -0
modified ai app/controlplane/pkg/auditor/events/project_test.go +30 / -1
modified ai app/controlplane/pkg/biz/projectversion_integration_test.go +28 / -0
modified ai app/controlplane/pkg/auditor/events/project.go +14 / -8
modified ai app/controlplane/pkg/data/workflowrun.go +11 / -10
created human app/controlplane/pkg/auditor/events/testdata/projects/project_version_marked_as_latest.json +19 / -0
created human app/controlplane/pkg/auditor/events/testdata/projects/project_version_updated_generic.json +19 / -0
modified ai app/controlplane/pkg/biz/workflowrun.go +11 / -4
modified ai app/controlplane/pkg/biz/auditor.go +11 / -3
modified human app/controlplane/pkg/auditor/events/testdata/projects/project_version_updated.json +3 / -2

Policies (4, 1 failing)

Status Policy Material Messages
✅ Passed ai-config-ai-agents-allowed ai-coding-session-822618 -
✅ Passed ai-config-no-dangerous-commands ai-coding-session-822618 -
⚠️ Failed ai-config-no-secrets ai-coding-session-822618
  • Potential secret (JWT) found in session content [turn=523, source=tool_result, line=17, value=eyJhbGci...izJA]
  • Potential secret (JWT) found in session content [turn=527, source=tool_result, line=4, value=eyJhbGci...wado]
  • Potential secret (JWT) found in session content [turn=605, source=tool_result, line=1, value=eyJhbGci...rzGI]
  • Potential secret (Quoted API key/password) found in session content [turn=513, source=tool_result, line=74, value=secret: ...mV0"]
  • Potential secret (Quoted API key/password) found in session content [turn=523, source=tool_result, line=17, value=token = ...zJA']
✅ Passed ai-config-mcp-servers-allowed ai-coding-session-822618 -

Powered by Chainloop and Chainloop Trace

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 17 files

Re-trigger cubic

The comment claimed the platform builds its "project version mark-latest"
command and service endpoint on top of this method. That is not the case: the
platform implements its own mark-latest over its own repositories and does not
reference this use case at all.

The inaccuracy is not harmless. It led a review of the surrounding change to
treat these methods as a live customer-facing path and to file follow-up work
on that basis. State what is actually true instead: nothing calls this today,
and it is kept as the biz-layer entry point with the promotion event wired so a
future caller cannot silently reintroduce the tracking gap.

Assisted-by: Claude Code
Signed-off-by: Javier Rodriguez <javier@chainloop.dev>

Chainloop-Trace-Sessions: 82261846-ded9-4e52-8707-d8a769402b5a
@javirln
javirln merged commit 91aeeb2 into chainloop-dev:main Jul 31, 2026
15 of 16 checks passed
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.

2 participants