Admin Probe Version Manager — Interface Spec & Implementation Plan¶
Purpose¶
VeriLib runs multiple proof languages (Lean, Verus, Aeneas, Dafny, …). Each language can have several installed probe / verifier toolchain versions. Today those versions are stored in verifierversions and selected per repo via repos.verifierversion_id, but there is no admin UI to manage them, no write API, and no way to install new probe releases without manual backend work.
This document defines an admin interface in VeriLib FE (role-gated) that lets authorized users:
- See which probe versions are installed on the atomizer backend
- Control which versions are available for selection
- Set defaults at global (per proof language), user, or repo scope
- Request download / installation of newer probe releases from the backend
The atomizer worker remains responsible for actually running probes; the frontend and VeriLib API own catalog, policy, and defaults.
Problem statement¶
| Constraint | Why it matters |
|---|---|
| One probe version per language is not enough | Lean 4.7 vs 4.14, Verus stable vs prerelease, Aeneas pins, etc. |
| Repos need different versions | Experiments, migration, customer-specific pins |
| Deploying a new probe today is operational | Requires backend/infra changes; no self-service path for admins |
| Upload UI already exposes a version dropdown | Data comes from GET /v2/verifier/versions/{prooflanguage_id} but catalog is static / DB-seeded |
Goal: make probe version management a first-class admin capability without blocking on a full "admin portal" — start with probe versions only; other admin tools can follow the same pattern.
Current state (VeriLib FE)¶
Version selection flow¶
- Upload / edit UI loads proof languages from
GET /v2/languages. - UI loads versions from
GET /v2/verifier/versions/{prooflanguage_id}(VerifierController::getVersions). - User picks
verifierversion_id; stored onrepos.verifierversion_id. - Queue messages (
UploadRequestMessage,UploadAtomizeRequest) carryverifier_version_idandverified_version(string fromverifierversions.version). - External atomizer worker maps
verified_version→ Docker image / probe binary (outside this repo).
Data model¶
verifierversions (no formal migration in sql/; seeded in dev):
| Column | Usage |
|---|---|
id |
PK; repos.verifierversion_id FK |
language_id |
Filter for listing — semantics inconsistent (seed uses 'lean' string; API receives numeric proof language id) |
version |
Toolchain string sent to worker (e.g. 4.7.0, stable) |
sortorder |
Display order |
user_id |
Creator / audit |
repos.verifierversion_id — per-repo selection.
Separate: versions table (compiler display versions) — not probe versions.
Authorization patterns to reuse¶
| Pattern | Location | Notes |
|---|---|---|
| Platform role | users.role_id (1=admin, 2=moderator, 3=user) |
Admins bypass unpublished repo access |
| Task permissions | tasks + permissions |
e.g. Project Admin (tasks.slug), Feature Repo, Certifier |
| Slug-scoped admin API | ProjectScreenController |
GET/PUT /v2/project-screen/:slug, Users::userCanAdminProjectScreen() |
| Repo RBAC | PermissionsConfig, RBACMiddleware |
Owner/editor/viewer — not for platform admin |
Gaps¶
- Read-only version API; no CRUD
- No admin permission task for probe management
- No UI beyond upload dropdown
language_idonverifierversionsvs numericprooflanguage_idmismatch- Changing
verifierversion_idon repo update does not auto re-atomize - Version list endpoint is unauthenticated
- Worker install / download not exposed via API
- TS type
VerifierVersion.prooflanguage_iddoes not match API response
Target architecture¶
┌─────────────────────────────────────────────────────────────────┐
│ VeriLib FE — Admin Probe Version Manager (React) │
│ /admin/probe-versions (role: Probe Version Admin or admin) │
└────────────────────────────┬────────────────────────────────────┘
│ REST
┌────────────────────────────▼────────────────────────────────────┐
│ VeriLib API (verilib-frontend) │
│ • Catalog CRUD (verifierversions + defaults) │
│ • Proxy: list installed probes on atomizer │
│ • Proxy: trigger probe download / install │
└────────────┬───────────────────────────────┬────────────────────┘
│ │
▼ ▼
┌────────────────────────┐ ┌────────────────────────────────────┐
│ MySQL │ │ Atomizer admin API (new) │
│ verifierversions │ │ GET /admin/probes/installed │
│ probe_defaults (new) │ │ GET /admin/probes/releases │
│ users / repos pins │ │ POST /admin/probes/install │
└────────────────────────┘ └────────────────────────────────────┘
│
▼
┌────────────────────────┐
│ Queue workers │
│ upload / atomize │ ← consume verified_version from message
└────────────────────────┘
Separation of concerns
- FE: catalog UI, defaults UI, install requests, audit display
- VeriLib API: permissions, persistence, validation, orchestration
- Atomizer: filesystem / Docker images, listing installed artifacts, pulling releases
Admin interface specification¶
Access¶
- Route:
/admin/probe-versions(or under existing admin nav when it exists) - Visible only if
Users::userCanManageProbeVersions($userId)orusers.role_id === 1(admin) - All mutating API routes require the same check + session auth
- Non-admins: 404 or redirect (prefer 404 to avoid leaking feature existence)
Page layout¶
Header¶
- Title: Probe versions
- Subtitle: manage installed probe toolchains and defaults per proof language
Section A — Proof language selector¶
- Tabs or dropdown: Lean, Verus, Aeneas, Dafny, … (from
GET /v2/languageswheretypeisprooforboth) - Shows active proof language id (numeric, canonical)
Section B — Installed catalog (VeriLib DB)¶
Table columns:
| Column | Description |
|---|---|
| Version | verifierversions.version (display + worker string) |
| Label | Optional human label (new column) |
| Docker tag / artifact ref | Optional mapping for worker (new column) |
| Status | available / disabled |
| Default | badge if global default for this language |
| Sort order | drag or numeric |
| Installed on backend | yes/no (from atomizer installed list) |
| Actions | Edit, Disable, Set as default, Delete (if unused) |
Actions:
- Add version — manual row (version string, label, docker tag, sort order)
- Sync from backend — merge atomizer installed list into catalog (mark installed, offer import)
- Set as global default — writes
probe_defaultsrow
Section C — Available upstream releases (atomizer)¶
- List from atomizer
GET /admin/probes/releases?language=lean(or proof language key) - Columns: release tag, published date, already installed
- Action: Install →
POSTinstall on atomizer, poll job status, refresh installed list - Optional: Install and add to catalog — install + create
verifierversionsrow + set available
Section D — Defaults hierarchy (read-focused v1; edit in v2)¶
Display effective default resolution for selected language:
- Repo override (
repos.verifierversion_idwhen set explicitly) - User override (new:
user_probe_defaults) - Global default (
probe_defaults)
v1 can show global default only; repo/user overrides remain existing upload flow until phase 2.
Section E — Audit / activity (optional v1)¶
- Last install request, user, timestamp, outcome
- Stored in
probe_install_jobsor atomizer callback
Upload / edit repo UX (consumer, not admin page)¶
- Dropdown continues to use
GET /v2/verifier/versions/{prooflanguage_id}but returns onlyavailableversions, sorted bysortorder - Pre-select: repo's
verifierversion_id, else resolved default for user/language - If repo's pinned version is disabled, show warning + force re-select
Data model changes¶
Migration sql/34.sql (proposed)¶
Fix and extend verifierversions:
-- Canonical FK to languages.id (proof language)
ALTER TABLE verifierversions
ADD COLUMN prooflanguage_id INT NULL AFTER id,
ADD COLUMN label VARCHAR(128) NULL AFTER version,
ADD COLUMN docker_tag VARCHAR(128) NULL AFTER label,
ADD COLUMN is_available TINYINT(1) NOT NULL DEFAULT 1 AFTER docker_tag,
ADD COLUMN created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
ADD INDEX idx_verifierversions_prooflanguage (prooflanguage_id, sortorder);
-- Backfill prooflanguage_id from legacy language_id where possible
-- (one-time UPDATE joining languages.name — run in migration script)
Global defaults:
CREATE TABLE probe_defaults (
prooflanguage_id INT NOT NULL PRIMARY KEY,
verifierversion_id INT NOT NULL,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
updated_by INT NULL,
FOREIGN KEY (verifierversion_id) REFERENCES verifierversions(id)
);
User defaults (phase 2):
CREATE TABLE user_probe_defaults (
user_id INT NOT NULL,
prooflanguage_id INT NOT NULL,
verifierversion_id INT NOT NULL,
PRIMARY KEY (user_id, prooflanguage_id)
);
Install job log (optional):
CREATE TABLE probe_install_jobs (
id INT AUTO_INCREMENT PRIMARY KEY,
prooflanguage_id INT NOT NULL,
release_tag VARCHAR(128) NOT NULL,
status ENUM('pending','running','succeeded','failed') NOT NULL DEFAULT 'pending',
requested_by INT NULL,
atomizer_job_id VARCHAR(64) NULL,
error_message TEXT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
finished_at TIMESTAMP NULL
);
Permissions seed:
INSERT INTO tasks (label, description, slug, sortorder, server, user_id)
SELECT 'Probe Version Admin', 'Manage probe versions and defaults', 'probe-versions', 54, '0', NULL
FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM tasks WHERE slug = 'probe-versions');
INSERT IGNORE INTO permissions (task_id, workeruser_id, status, manageruser_id)
SELECT t.id, u.id, 1, NULL
FROM users u JOIN tasks t ON t.slug = 'probe-versions'
WHERE u.role_id = 1;
Backend API (VeriLib FE)¶
Public / upload (existing, hardened)¶
| Method | Path | Change |
|---|---|---|
| GET | /v2/verifier/versions/{prooflanguage_id} |
Filter is_available=1; fix join on prooflanguage_id; require auth optional (recommend auth) |
Admin catalog¶
| Method | Path | Description |
|---|---|---|
| GET | /v2/admin/probe-versions |
List all versions; query ?prooflanguage_id= |
| POST | /v2/admin/probe-versions |
Create catalog entry |
| PUT | /v2/admin/probe-versions/{id} |
Update label, docker_tag, sortorder, is_available |
| DELETE | /v2/admin/probe-versions/{id} |
Soft-delete or hard-delete if no repos reference |
| PUT | /v2/admin/probe-defaults/{prooflanguage_id} |
Set global default { verifierversion_id } |
| GET | /v2/admin/probe-defaults |
List global defaults |
All routes: CheckUserMiddleware + Users::userCanManageProbeVersions().
Atomizer proxy (VeriLib → atomizer)¶
| Method | Path | Description |
|---|---|---|
| GET | /v2/admin/probe-versions/installed |
Proxy atomizer installed list |
| GET | /v2/admin/probe-versions/releases |
Proxy upstream releases |
| POST | /v2/admin/probe-versions/install |
Body: { prooflanguage_id, release_tag }; creates job row, calls atomizer |
Implement in ProbeVersionController mirroring ProjectScreenController structure.
Users.php addition:
public static function userCanManageProbeVersions(?int $userId): bool
{
if ($userId === null) return false;
if (/* role_id === 1 */) return true;
return self::userHasTaskPermission($userId, 'Probe Version Admin');
}
Atomizer backend (separate repo — contract)¶
New admin endpoints on atomizer service (authenticated service-to-service or forwarded user token):
| Method | Path | Description |
|---|---|---|
| GET | /admin/probes/installed |
[{ language, version, docker_tag, path, installed_at }] |
| GET | /admin/probes/releases |
Query language; list GitHub/container registry releases |
| POST | /admin/probes/install |
{ language, release_tag } → async job id |
| GET | /admin/probes/install/{job_id} |
Job status |
Install script (atomizer): CLI or internal module that:
- Resolves release tag for language (Lean toolchain repo, Verus image, Aeneas pin file, …)
- Pulls Docker image or downloads binary bundle
- Registers installed version in local manifest (atomizer reads this when validating queue messages)
VeriLib FE does not pull images directly; it only triggers atomizer.
Frontend implementation (React)¶
New files¶
| File | Purpose |
|---|---|
src/pages/admin/ProbeVersionAdmin.tsx |
Main page |
src/components/admin/ProbeVersionTable.tsx |
Catalog table |
src/components/admin/ProbeReleasePanel.tsx |
Upstream releases + install |
src/components/admin/ProbeDefaultSelector.tsx |
Global default control |
src/services/probeVersionApi.ts |
Admin API client |
Route in App router |
/admin/probe-versions |
Patterns to copy¶
- Permission gate: same as Signal Shot
can_editfrom API, or loadGET /v2/admin/probe-versions/metareturning{ can_manage: bool } - Forms:
SignalShotConfigEditormodal pattern for edit row - Tables: repo stats / upload form select options
API hooks¶
useQuery(['probeVersions', prooflanguageId])useMutationfor create/update/install with toast + refetch- Poll install job status every N seconds until terminal state
Repo update behavior (recommended change)¶
Today RepoController::update does not re-queue atomize when only verifierversion_id changes.
Add:
- If
verifierversion_idchanges and user confirms (or admin policy flag), enqueue re-atomize similar toPOST /v2/repo/reatomize/:repo_id - Upload UI: checkbox "Re-run atomization with new probe version"
Document in admin UI that changing global default does not mutate existing repos.
Implementation plan¶
Phase 1 — Catalog & permissions (foundation)¶
Database
- Add
sql/34.sql: extendverifierversions, createprobe_defaults, task + permission seed - Backfill
prooflanguage_idfrom legacylanguage_idvalues
Backend
ProbeVersionControllerwith GET/POST/PUT/DELETE catalog + GET/PUT defaultsUsers::userCanManageProbeVersions()- Fix
VerifierController::getVersionsto queryprooflanguage_idandis_available=1 - Register routes in
public/app/routes.phpwith auth middleware
Frontend
- Admin page: language tabs, catalog table, add/edit/disable, set global default
- No atomizer install yet — manual DB / seed still OK
Exit criteria
- Admin can enable/disable versions and set global default per proof language
- Upload dropdown reflects catalog changes
- Only Probe Version Admin (and platform admin) can access admin routes
Phase 2 — Atomizer integration (install & sync)¶
Atomizer repo
- Implement installed list, releases list, install job API
- Manifest file co-located with worker images
VeriLib API
- Proxy endpoints +
probe_install_jobslogging - "Sync from backend" merges installed into catalog
Frontend
- Releases panel, Install button, job status polling
- "Installed on backend" column populated
Exit criteria
- Admin can install a new release from UI without SSH / hackathon deploy
- Installed state visible in catalog
Phase 3 — Defaults hierarchy & repo pins¶
Database
user_probe_defaultstable
Backend
- CRUD for user defaults (admin sets for any user, or user self-service later)
- Helper
ProbeVersionResolver::resolve($userId, $repoId, $prooflanguageId)used by upload create + edit-info
Frontend
- User default column in admin (optional)
- Upload form: show effective default source (repo / user / global)
Repo update
- Re-atomize on version change (with confirmation)
Exit criteria
- New repos get correct default without manual dropdown selection
- Per-repo override still works via existing upload/edit
Phase 4 — Hardening & ops¶
- Authenticate public version list or rate-limit
- Prevent delete of version referenced by repos (FK or check + archive)
- Audit log for catalog changes
- Align TS types with API (
VerifierVersionincludesprooflanguage_id,is_available,label) - Documentation for mapping
versionstring → worker Docker tag per language - Staging/prod migration runbook (
sql/34.sqlviascripts/run-migrations.sh)
Open decisions¶
| Decision | Options | Recommendation |
|---|---|---|
| Admin vs moderator | Probe Version Admin task only vs role_id=1 only |
Task + auto-grant admins (same as Project Admin) |
| Delete vs disable | Hard delete vs is_available=0 |
Disable only; delete if zero repo references |
language_id migration |
Backfill vs new column only | Add prooflanguage_id, deprecate language_id |
| Install scope | Per language server vs global atomizer pool | Match current queue partition (Lean/Aeneas/Verus workers) |
| User self-service defaults | Phase 3 admin-only vs user settings page | Admin-only first |
Related files (reference)¶
| Area | Path |
|---|---|
| Version list API | public/app/Controllers/Verifier/VerifierController.php |
| Upload version pick | react-graph-standard/src/components/upload/UploadForm.tsx |
| Repo version field | public/app/Controllers/Repo/RepoController.php |
| Queue payload | public/app/Lib/Queue/UploadRequestMessage.php |
| Permission pattern | public/app/Models/Users.php, sql/32.sql |
| Admin screen pattern | public/app/Controllers/ProjectScreen/ProjectScreenController.php |
| Atomize consumer | public/app/Services/AtomizeService.php |
Out of scope (for this feature)¶
- Full general-purpose admin portal (users, queues, certificates, featured repos)
- Cert page hardcoded
probe_verus_versionmanifests (stats.php, cert5050) — separate unification - Automatic upgrade of all repos when global default changes
- Probe version management inside atomizer worker runtime (only install/list API)
Success metrics¶
- Admin can add/disable a probe version without DB access
- Admin can install a new release without infra SSH
- Upload flow shows only allowed versions for selected proof language
- Existing repos keep their pin until explicitly changed or re-atomized
- Clear audit trail of install requests and catalog edits