fix: the app head is the tail of app.versions, not the newest timestamp

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ruben Fiszel
2026-09-16 09:13:01 +02:00
co-authored by Claude Opus 5
parent 63cc0181de
commit dbb63cfb36
4 changed files with 65 additions and 12 deletions
@@ -0,0 +1,47 @@
{
"db_name": "PostgreSQL",
"query": "SELECT a.id as app_id, av.id as version_id, dm.deployment_msg as deployment_msg,\n av.created_by as created_by, av.created_at as created_at\n FROM app a JOIN app_version av ON av.id = a.versions[array_upper(a.versions, 1)]\n LEFT JOIN deployment_metadata dm ON av.id = dm.app_version\n WHERE a.workspace_id = $1 AND a.path = $2",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "app_id",
"type_info": "Int8"
},
{
"ordinal": 1,
"name": "version_id",
"type_info": "Int8"
},
{
"ordinal": 2,
"name": "deployment_msg",
"type_info": "Text"
},
{
"ordinal": 3,
"name": "created_by",
"type_info": "Varchar"
},
{
"ordinal": 4,
"name": "created_at",
"type_info": "Timestamptz"
}
],
"parameters": {
"Left": [
"Text",
"Text"
]
},
"nullable": [
false,
false,
true,
false,
false
]
},
"hash": "1461a023390f674b0d2e286b506c0838c7cad23619984f44fb4c88d377acffc7"
}
+10 -4
View File
@@ -1273,15 +1273,21 @@ async fn get_latest_version(
let path = path.to_path();
check_scopes(&authed, || format!("apps:read:{}", path))?;
let mut tx = user_db.begin(&authed).await?;
// The head is the tail of `app.versions` — the version the runtime serves. Deploys
// append to it under the app row's lock, whereas `app_version.created_at` is the
// deploying transaction's start time, so two that overlap can carry it in either
// order and the newest timestamp is then not the one that landed last.
let row = sqlx::query!(
"SELECT a.id as app_id, av.id as version_id, dm.deployment_msg as deployment_msg,
av.created_by as created_by, av.created_at as created_at
FROM app a LEFT JOIN app_version av ON a.id = av.app_id LEFT JOIN deployment_metadata dm ON av.id = dm.app_version
WHERE a.workspace_id = $1 AND a.path = $2
ORDER BY av.created_at DESC",
FROM app a JOIN app_version av ON av.id = a.versions[array_upper(a.versions, 1)]
LEFT JOIN deployment_metadata dm ON av.id = dm.app_version
WHERE a.workspace_id = $1 AND a.path = $2",
w_id,
path,
).fetch_optional(&mut *tx).await?;
)
.fetch_optional(&mut *tx)
.await?;
tx.commit().await?;
if let Some(row) = row {
@@ -393,13 +393,13 @@
custom_path: customPath,
labels: $state.snapshot(labels)
}
const appHistory = await AppService.getAppHistoryByPath({
const head = await AppService.getAppLatestVersion({
workspace: $workspaceStore!,
path: npath
})
// `version` is what is deployed now, which is the deploy guard's fallback head; the
// deploy's own answer is the base, and the two differ when another landed beside it.
version = appHistory[0]?.version
version = head?.version
// Re-pin the fork base to the version just written: the editor stays open, so a
// follow-up deploy (or a new edit) would otherwise compare against the now-
// superseded base and falsely warn. parent_version is in
@@ -410,8 +410,8 @@
onDeploy?.({
version: deployed.version,
head: version,
headBy: appHistory[0]?.created_by,
headAt: appHistory[0]?.created_at
headBy: head?.created_by,
headAt: head?.created_at
})
closeSaveDrawer()
@@ -548,14 +548,14 @@
custom_path: customPath,
labels: $state.snapshot(labels)
}
const appHistory = await AppService.getAppHistoryByPath({
const head = await AppService.getAppLatestVersion({
workspace: opWorkspace!,
path: npath
})
// The deploy's own answer is the next draft's base; `version` is what is deployed
// now, and the two differ when another deploy landed beside this one. The route
// owns this prop and re-pushes `parentVersion ?? head` as soon as `onDeploy` returns.
version = appHistory[0]?.version
version = head?.version
closeSaveDrawer()
sendUserToast('App deployed successfully')
@@ -574,8 +574,8 @@
path: npath,
version: deployed.version,
head: version,
headBy: appHistory[0]?.created_by,
headAt: appHistory[0]?.created_at
headBy: head?.created_by,
headAt: head?.created_at
})
}