feat(kiloclaw): add image tag based version pinning#483
feat(kiloclaw): add image tag based version pinning#483
Conversation
Add admin-controlled per-user version pinning with image_tag as the unique deployable identity and image_digest for content integrity. - New DB tables: kiloclaw_available_versions (catalog), kiloclaw_version_pins (per user pins) - Admin versions router: CRUD for catalog entries, pin/unpin users, publish versions - Cron sync: worker KV → Postgres catalog with atomic upserts and digest conflict rejection - Provision flow: cloud reads pin from Postgres, passes tag+digest to worker - Worker: accepts pinnedImageTag, skips KV lookup for pinned users, tracks isPinned - Digest uniqueness enforced at every layer (Postgres, admin publish, sync, worker KV) - Admin UI: pin management on instance detail, version stats cards, sync button - Tests for all new functionality
Code Review SummaryStatus: 2 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Other Observations (not in diff)Issues found in unchanged code that cannot receive inline comments:
Files Reviewed (28 files)
|
- Move digest conflict check inside transaction with FOR UPDATE lock
to prevent TOCTOU race in publishVersion
- Add paginated KV list helper for catalogs beyond 1000 entries
- Fix sync rate limit to skip only new inserts, not updates
- Add kiloclaw_version_pins cleanup to softDeleteUser for GDPR
compliance (with test)
…ag-pinning merge: resolve conflicts with main Renumber version pinning migration from 0024 to 0028 to avoid collision with migrations landed on main (0024-0027). Regenerated snapshot with correct parent chain. Both SCHEMA_CHECK_ENUMS (KiloClawVersionStatus + SecurityAuditLogAction) included
…ag-pinning merge: resolve migration conflicts with main Renumber 0028_skinny_vin_gonzales to 0029 to avoid collision with main's 0028_wakeful_mercury migration. Updated snapshot chain and schema state accordingly.
| const catalogEntry = await db.query.kiloclaw_available_versions.findFirst({ | ||
| where: and( | ||
| eq(kiloclaw_available_versions.image_tag, pin.image_tag), | ||
| eq(kiloclaw_available_versions.status, 'active') |
There was a problem hiding this comment.
WARNING: Deprecated pins are silently ignored during provisioning
pinUser currently allows pinning to versions with status = 'deprecated', but this query only honors pins when the catalog row is active. That means admins can successfully create a pin that will never be applied, and the user silently falls back to latest.
This behavior mismatch can cause hard-to-debug rollout issues. Consider either rejecting deprecated pins in pinUser or allowing deprecated status here and only rejecting disabled.
| // Reject entries whose digest already belongs to a different tag. | ||
| // Same digest = same image. Two tags with the same digest is not allowed. | ||
| if (entry.imageDigest) { | ||
| const conflict = await db.query.kiloclaw_available_versions.findFirst({ |
There was a problem hiding this comment.
WARNING: Digest conflict check is still race-prone under concurrent sync runs
The conflict read is done before the insert/upsert, but not inside a transaction/lock scope. If two sync executions run concurrently (cron overlap or manual trigger + cron), both can pass this check and then race into the unique digest index, causing a raw DB constraint error and failing the sync run.
Consider moving this conflict check into a transaction with row locking (similar to publishVersion) or handling unique-constraint errors explicitly and continuing safely.
Add admin-controlled per-user version pinning with image_tag as the unique deployable identity and image_digest for content integrity.