feat: project Runtime verification state

This commit is contained in:
2026-09-08 09:22:44 +09:00
parent f29c343879
commit 7b6a84a550
10 changed files with 596 additions and 57 deletions
+52
View File
@@ -1597,11 +1597,51 @@ pub enum WorkspaceRuntimeAuthenticationMode {
WorkspaceIdentity, WorkspaceIdentity,
} }
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(rename_all = "snake_case")]
pub enum RuntimeConnectionDisplayState {
Configured,
Verified,
Unavailable,
Revoked,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(rename_all = "snake_case")]
pub enum RuntimeVerificationOutcome {
Verified,
ChallengeIssued,
VerificationFailed,
ConnectivityFailed,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(deny_unknown_fields)]
pub struct RuntimeVerificationEvidenceSummary {
pub verified_at: Option<String>,
pub last_checked_at: String,
pub last_outcome: RuntimeVerificationOutcome,
#[cfg_attr(feature = "typescript", ts(type = "number"))]
pub binding_revision: u64,
pub workspace_key_id: String,
#[cfg_attr(feature = "typescript", ts(type = "number"))]
pub workspace_identity_revision: u64,
#[cfg_attr(feature = "typescript", ts(type = "number"))]
pub workspace_trust_generation: u64,
pub runtime_public_key_fingerprint: String,
#[cfg_attr(feature = "typescript", ts(type = "number"))]
pub runtime_identity_revision: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "typescript", derive(ts_rs::TS))] #[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct WorkspaceRuntimeBindingSummary { pub struct WorkspaceRuntimeBindingSummary {
pub state: WorkspaceRuntimeBindingState, pub state: WorkspaceRuntimeBindingState,
pub connection_state: RuntimeConnectionDisplayState,
pub authentication_mode: WorkspaceRuntimeAuthenticationMode, pub authentication_mode: WorkspaceRuntimeAuthenticationMode,
#[cfg_attr(feature = "typescript", ts(type = "number"))] #[cfg_attr(feature = "typescript", ts(type = "number"))]
pub revision: u64, pub revision: u64,
@@ -1610,6 +1650,8 @@ pub struct WorkspaceRuntimeBindingSummary {
#[serde(default, skip_serializing_if = "Option::is_none")] #[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg_attr(feature = "typescript", ts(type = "number | null"))] #[cfg_attr(feature = "typescript", ts(type = "number | null"))]
pub workspace_key_generation: Option<u64>, pub workspace_key_generation: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub verification: Option<RuntimeVerificationEvidenceSummary>,
} }
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
@@ -1794,6 +1836,10 @@ pub enum RuntimeConnectionTestFailureKind {
pub struct RuntimeConnectionTestResponse { pub struct RuntimeConnectionTestResponse {
pub workspace_id: String, pub workspace_id: String,
pub runtime_id: String, pub runtime_id: String,
#[cfg_attr(feature = "typescript", ts(type = "number"))]
pub binding_revision: u64,
pub connection_state: RuntimeConnectionDisplayState,
pub verification: Option<RuntimeVerificationEvidenceSummary>,
pub checked_at: String, pub checked_at: String,
pub status: RuntimeConnectionTestStatus, pub status: RuntimeConnectionTestStatus,
pub failure_kind: Option<RuntimeConnectionTestFailureKind>, pub failure_kind: Option<RuntimeConnectionTestFailureKind>,
@@ -2977,6 +3023,9 @@ pub fn catalog_typescript() -> String {
RuntimeSourceSummary::decl(&config), RuntimeSourceSummary::decl(&config),
RuntimeSummary::decl(&config), RuntimeSummary::decl(&config),
WorkspaceRuntimeBindingState::decl(&config), WorkspaceRuntimeBindingState::decl(&config),
RuntimeConnectionDisplayState::decl(&config),
RuntimeVerificationOutcome::decl(&config),
RuntimeVerificationEvidenceSummary::decl(&config),
WorkspaceRuntimeAuthenticationMode::decl(&config), WorkspaceRuntimeAuthenticationMode::decl(&config),
WorkspaceRuntimeBindingSummary::decl(&config), WorkspaceRuntimeBindingSummary::decl(&config),
RuntimeManagementSummary::decl(&config), RuntimeManagementSummary::decl(&config),
@@ -3842,6 +3891,9 @@ mod tests {
let compatible = serde_json::json!({ let compatible = serde_json::json!({
"workspace_id": "workspace-test", "workspace_id": "workspace-test",
"runtime_id": "runtime-test", "runtime_id": "runtime-test",
"binding_revision": 3,
"connection_state": "verified",
"verification": null,
"checked_at": "2026-09-01T12:00:00Z", "checked_at": "2026-09-01T12:00:00Z",
"status": "compatible", "status": "compatible",
"failure_kind": null, "failure_kind": null,
+173 -29
View File
@@ -85,13 +85,13 @@ use workspace_api::{
PutRuntimeTrustKeyRequest, RepositoryAccessProjection, RepositoryDetailResponse, PutRuntimeTrustKeyRequest, RepositoryAccessProjection, RepositoryDetailResponse,
RepositoryListResponse, RepositoryLogResponse, RepositorySshCredential, RepositorySshHostTrust, RepositoryListResponse, RepositoryLogResponse, RepositorySshCredential, RepositorySshHostTrust,
RequestActor, RevokeRuntimeTrustKeyRequest, RotateRepositorySshCredentialRequest, RequestActor, RevokeRuntimeTrustKeyRequest, RotateRepositorySshCredentialRequest,
RuntimeConnectionTestFailureKind, RuntimeConnectionTestResponse, RuntimeConnectionTestStatus, RuntimeConnectionDisplayState, RuntimeConnectionTestFailureKind, RuntimeConnectionTestResponse,
RuntimeManagementSummary, RuntimeTrustAuditAction, RuntimeTrustAuditEntry, RuntimeConnectionTestStatus, RuntimeManagementSummary, RuntimeTrustAuditAction,
RuntimeTrustConflictKind, RuntimeTrustConflictResponse, RuntimeTrustKeyRevealResponse, RuntimeTrustAuditEntry, RuntimeTrustConflictKind, RuntimeTrustConflictResponse,
RuntimeTrustKeyState, RuntimeTrustKeyStatus, TICKET_ORCHESTRATION_PLANS_QUERY_PATH, RuntimeTrustKeyRevealResponse, RuntimeTrustKeyState, RuntimeTrustKeyStatus,
TICKET_RELATIONS_QUERY_PATH, UpdateWorkspaceMetadataRequest, WhoamiResponse, TICKET_ORCHESTRATION_PLANS_QUERY_PATH, TICKET_RELATIONS_QUERY_PATH,
WorkerLaunchOptionsResponse, WorkerLaunchProfileCandidate, WorkerLaunchRuntimeOption, UpdateWorkspaceMetadataRequest, WhoamiResponse, WorkerLaunchOptionsResponse,
WorkerLaunchWorkerSummary, WorkerLaunchProfileCandidate, WorkerLaunchRuntimeOption, WorkerLaunchWorkerSummary,
WorkingDirectoryCreateRequest as BrowserWorkingDirectoryCreateRequest, WorkingDirectoryCreateRequest as BrowserWorkingDirectoryCreateRequest,
WorkingDirectoryCreateResponse as BrowserWorkingDirectoryCreateResponse, WorkingDirectoryCreateResponse as BrowserWorkingDirectoryCreateResponse,
WorkingDirectoryDetailResponse as BrowserWorkingDirectoryDetailResponse, WorkingDirectoryDetailResponse as BrowserWorkingDirectoryDetailResponse,
@@ -12054,16 +12054,24 @@ async fn scoped_delete_remote_runtime(
async fn scoped_test_runtime_connection( async fn scoped_test_runtime_connection(
State(api): State<WorkspaceApi>, State(api): State<WorkspaceApi>,
AxumPath(path): AxumPath<ScopedRuntimePath>, AxumPath(path): AxumPath<ScopedRuntimePath>,
Extension(actor): Extension<RequestActor>,
) -> ApiResult<Json<RuntimeConnectionTestResponse>> { ) -> ApiResult<Json<RuntimeConnectionTestResponse>> {
validate_workspace_scope(&api, &path.workspace_id)?; validate_workspace_scope(&api, &path.workspace_id)?;
require_workspace_owner(
&api,
&path.workspace_id,
&actor,
"Runtime connection verification",
)
.await?;
let binding = api let binding = api
.store .store
.get_workspace_runtime_binding(&path.workspace_id, &path.runtime_id) .get_workspace_runtime_binding(&path.workspace_id, &path.runtime_id)
.await? .await?
.ok_or_else(|| Error::UnknownRuntime(path.runtime_id.clone()))?; .ok_or_else(|| Error::UnknownRuntime(path.runtime_id.clone()))?;
if binding.state != StoredRuntimeBindingState::Verified { if binding.state == StoredRuntimeBindingState::Revoked || binding.revoked_at.is_some() {
return Err(Error::RuntimeBindingConflict(format!( return Err(Error::RuntimeBindingConflict(format!(
"Runtime `{}` is not an authenticated verified connection candidate", "Runtime `{}` binding is revoked",
path.runtime_id path.runtime_id
)) ))
.into()); .into());
@@ -13930,16 +13938,18 @@ async fn test_runtime_connection(
.get_workspace_runtime_verification(api.workspace_id(), &runtime_id) .get_workspace_runtime_verification(api.workspace_id(), &runtime_id)
.await .await
{ {
evidence.state = "failed".to_string(); if evidence.state != "verified" {
evidence.state = "failed".to_string();
evidence.verified_at = None;
}
evidence.last_outcome = "verification_failed".to_string(); evidence.last_outcome = "verification_failed".to_string();
evidence.verified_at = None;
evidence.checked_at = Utc::now().to_rfc3339(); evidence.checked_at = Utc::now().to_rfc3339();
let _ = api let _ = api
.store .store
.record_workspace_runtime_verification_attempt(&evidence) .record_workspace_runtime_verification_attempt(&evidence)
.await; .await;
} }
return Ok(Json(runtime_connection_test_failure( let mut result = runtime_connection_test_failure(
api.workspace_id(), api.workspace_id(),
&runtime_id, &runtime_id,
Utc::now().to_rfc3339(), Utc::now().to_rfc3339(),
@@ -13950,7 +13960,10 @@ async fn test_runtime_connection(
"error", "error",
message, message,
), ),
))); );
result.binding_revision = binding.binding_revision;
result.connection_state = RuntimeConnectionDisplayState::Unavailable;
return Ok(Json(result));
} }
} }
} }
@@ -13966,12 +13979,41 @@ async fn test_runtime_connection(
message: "Runtime connection test could not be completed".to_string(), message: "Runtime connection test could not be completed".to_string(),
})?; })?;
Ok(Json(runtime_connection_test_response( if ping.is_err()
api.workspace_id(), && binding.authentication_mode == StoredRuntimeAuthenticationMode::WorkspaceIdentity
&runtime_id, && let Some(mut evidence) = api
checked_at, .store
ping, .get_workspace_runtime_verification(api.workspace_id(), &runtime_id)
))) .await?
{
evidence.last_outcome = "connectivity_failed".to_string();
evidence.checked_at = Utc::now().to_rfc3339();
api.store
.record_workspace_runtime_verification_attempt(&evidence)
.await?;
}
let current_binding = api
.store
.get_workspace_runtime_binding(api.workspace_id(), &runtime_id)
.await?
.ok_or_else(|| Error::UnknownRuntime(runtime_id.clone()))?;
let verification = api
.store
.get_workspace_runtime_verification(api.workspace_id(), &runtime_id)
.await?;
let summary = runtime_binding_summary(&current_binding, verification.as_ref());
let mut result =
runtime_connection_test_response(api.workspace_id(), &runtime_id, checked_at, ping);
result.binding_revision = current_binding.binding_revision;
result.connection_state = if result.status == RuntimeConnectionTestStatus::Compatible {
summary.connection_state
} else if summary.connection_state == RuntimeConnectionDisplayState::Revoked {
RuntimeConnectionDisplayState::Revoked
} else {
RuntimeConnectionDisplayState::Unavailable
};
result.verification = summary.verification;
Ok(Json(result))
} }
async fn get_worker_launch_options( async fn get_worker_launch_options(
@@ -16070,6 +16112,16 @@ async fn workspace_runtime_resources_response(
.store .store
.list_workspace_runtime_bindings(workspace_id, true) .list_workspace_runtime_bindings(workspace_id, true)
.await?; .await?;
let mut verifications = HashMap::new();
for binding in &bindings {
if let Some(verification) = api
.store
.get_workspace_runtime_verification(workspace_id, &binding.runtime_id)
.await?
{
verifications.insert(binding.runtime_id.clone(), verification);
}
}
let mut items = runtimes let mut items = runtimes
.items .items
.into_iter() .into_iter()
@@ -16091,7 +16143,9 @@ async fn workspace_runtime_resources_response(
endpoint_configured: binding endpoint_configured: binding
.is_some_and(|binding| !binding.base_url.trim().is_empty()), .is_some_and(|binding| !binding.base_url.trim().is_empty()),
token_ref_configured: false, token_ref_configured: false,
binding: binding.map(runtime_binding_summary), binding: binding.map(|binding| {
runtime_binding_summary(binding, verifications.get(&binding.runtime_id))
}),
}, },
} }
}) })
@@ -16137,7 +16191,10 @@ async fn workspace_runtime_resources_response(
removable: true, removable: true,
endpoint_configured: !binding.base_url.trim().is_empty(), endpoint_configured: !binding.base_url.trim().is_empty(),
token_ref_configured: false, token_ref_configured: false,
binding: Some(runtime_binding_summary(&binding)), binding: Some(runtime_binding_summary(
binding,
verifications.get(&binding.runtime_id),
)),
}, },
}); });
} }
@@ -16151,13 +16208,38 @@ async fn workspace_runtime_resources_response(
}) })
} }
fn runtime_binding_summary(binding: &WorkspaceRuntimeBinding) -> WorkspaceRuntimeBindingSummary { fn runtime_binding_summary(
binding: &WorkspaceRuntimeBinding,
verification: Option<&crate::store::WorkspaceRuntimeVerificationEvidence>,
) -> WorkspaceRuntimeBindingSummary {
let valid_verification = verification.filter(|verification| {
verification.state == "verified"
&& verification.binding_revision == binding.binding_revision
&& verification.runtime_public_key_fingerprint == binding.public_key_fingerprint
&& verification.workspace_key_id
== binding.workspace_key_id.as_deref().unwrap_or_default()
});
let connection_state = match binding.state {
StoredRuntimeBindingState::Revoked => RuntimeConnectionDisplayState::Revoked,
_ if verification.is_some_and(|verification| verification.last_outcome != "verified") => {
RuntimeConnectionDisplayState::Unavailable
}
StoredRuntimeBindingState::Verified
if binding.authentication_mode
== StoredRuntimeAuthenticationMode::LegacyServerIssuer
|| valid_verification.is_some() =>
{
RuntimeConnectionDisplayState::Verified
}
_ => RuntimeConnectionDisplayState::Configured,
};
WorkspaceRuntimeBindingSummary { WorkspaceRuntimeBindingSummary {
state: match binding.state { state: match binding.state {
StoredRuntimeBindingState::Configured => WorkspaceRuntimeBindingState::Configured, StoredRuntimeBindingState::Configured => WorkspaceRuntimeBindingState::Configured,
StoredRuntimeBindingState::Verified => WorkspaceRuntimeBindingState::Verified, StoredRuntimeBindingState::Verified => WorkspaceRuntimeBindingState::Verified,
StoredRuntimeBindingState::Revoked => WorkspaceRuntimeBindingState::Revoked, StoredRuntimeBindingState::Revoked => WorkspaceRuntimeBindingState::Revoked,
}, },
connection_state,
authentication_mode: match binding.authentication_mode { authentication_mode: match binding.authentication_mode {
StoredRuntimeAuthenticationMode::LegacyServerIssuer => { StoredRuntimeAuthenticationMode::LegacyServerIssuer => {
WorkspaceRuntimeAuthenticationMode::LegacyServerIssuer WorkspaceRuntimeAuthenticationMode::LegacyServerIssuer
@@ -16169,9 +16251,33 @@ fn runtime_binding_summary(binding: &WorkspaceRuntimeBinding) -> WorkspaceRuntim
revision: binding.binding_revision, revision: binding.binding_revision,
workspace_key_id: binding.workspace_key_id.clone(), workspace_key_id: binding.workspace_key_id.clone(),
workspace_key_generation: binding.workspace_key_generation, workspace_key_generation: binding.workspace_key_generation,
verification: verification.and_then(runtime_verification_summary),
} }
} }
fn runtime_verification_summary(
verification: &crate::store::WorkspaceRuntimeVerificationEvidence,
) -> Option<workspace_api::RuntimeVerificationEvidenceSummary> {
let last_outcome = match verification.last_outcome.as_str() {
"verified" => workspace_api::RuntimeVerificationOutcome::Verified,
"challenge_issued" => workspace_api::RuntimeVerificationOutcome::ChallengeIssued,
"verification_failed" => workspace_api::RuntimeVerificationOutcome::VerificationFailed,
"connectivity_failed" => workspace_api::RuntimeVerificationOutcome::ConnectivityFailed,
_ => return None,
};
Some(workspace_api::RuntimeVerificationEvidenceSummary {
verified_at: verification.verified_at.clone(),
last_checked_at: verification.checked_at.clone(),
last_outcome,
binding_revision: verification.binding_revision,
workspace_key_id: verification.workspace_key_id.clone(),
workspace_identity_revision: verification.workspace_identity_revision,
workspace_trust_generation: verification.workspace_trust_generation,
runtime_public_key_fingerprint: verification.runtime_public_key_fingerprint.clone(),
runtime_identity_revision: verification.runtime_identity_revision,
})
}
async fn workspace_runtime_detail( async fn workspace_runtime_detail(
api: &WorkspaceApi, api: &WorkspaceApi,
workspace_id: &str, workspace_id: &str,
@@ -16213,7 +16319,7 @@ async fn workspace_runtime_detail(
removable: false, removable: false,
endpoint_configured: !binding.base_url.trim().is_empty(), endpoint_configured: !binding.base_url.trim().is_empty(),
token_ref_configured: false, token_ref_configured: false,
binding: Some(runtime_binding_summary(&binding)), binding: Some(runtime_binding_summary(&binding, None)),
}, },
}); });
} }
@@ -16463,6 +16569,9 @@ fn runtime_connection_test_response(
Ok(ping) => RuntimeConnectionTestResponse { Ok(ping) => RuntimeConnectionTestResponse {
workspace_id: workspace_id.to_string(), workspace_id: workspace_id.to_string(),
runtime_id: runtime_id.to_string(), runtime_id: runtime_id.to_string(),
binding_revision: 0,
connection_state: RuntimeConnectionDisplayState::Configured,
verification: None,
checked_at, checked_at,
status: RuntimeConnectionTestStatus::Compatible, status: RuntimeConnectionTestStatus::Compatible,
failure_kind: None, failure_kind: None,
@@ -16512,6 +16621,9 @@ fn runtime_connection_test_failure(
RuntimeConnectionTestResponse { RuntimeConnectionTestResponse {
workspace_id: workspace_id.to_string(), workspace_id: workspace_id.to_string(),
runtime_id: runtime_id.to_string(), runtime_id: runtime_id.to_string(),
binding_revision: 0,
connection_state: RuntimeConnectionDisplayState::Unavailable,
verification: None,
checked_at, checked_at,
status: RuntimeConnectionTestStatus::Failed, status: RuntimeConnectionTestStatus::Failed,
failure_kind: Some(failure_kind), failure_kind: Some(failure_kind),
@@ -20336,18 +20448,20 @@ mod tests {
generic_put.into_response().status(), generic_put.into_response().status(),
StatusCode::BAD_REQUEST StatusCode::BAD_REQUEST
); );
let configured_test = scoped_test_runtime_connection( let Json(configured_test) = scoped_test_runtime_connection(
State(api.clone()), State(api.clone()),
AxumPath(ScopedRuntimePath { AxumPath(ScopedRuntimePath {
workspace_id: api.config.workspace_id.clone(), workspace_id: api.config.workspace_id.clone(),
runtime_id: "configured-runtime".to_string(), runtime_id: "configured-runtime".to_string(),
}), }),
Extension(actor.clone()),
) )
.await .await
.unwrap_err(); .unwrap();
assert_eq!(configured_test.status, RuntimeConnectionTestStatus::Failed);
assert_eq!( assert_eq!(
configured_test.into_response().status(), configured_test.connection_state,
StatusCode::CONFLICT RuntimeConnectionDisplayState::Unavailable
); );
let (status, Json(replayed)) = create_remote_runtime( let (status, Json(replayed)) = create_remote_runtime(
@@ -27991,7 +28105,9 @@ mod tests {
) -> serde_json::Value { ) -> serde_json::Value {
let (endpoint, _server) = runtime_ping_stub(status, body).await; let (endpoint, _server) = runtime_ping_stub(status, body).await;
let dir = tempfile::tempdir().unwrap(); let dir = tempfile::tempdir().unwrap();
let app = test_app_with_remote_runtime(dir.path(), "probe-runtime", endpoint).await; let app = test_app_with_remote_runtime(dir.path(), "probe-runtime", endpoint)
.await
.layer(Extension(test_owner_actor()));
post_json( post_json(
app, app,
&format!("/api/w/{TEST_WORKSPACE_ID}/runtimes/probe-runtime/connection-tests"), &format!("/api/w/{TEST_WORKSPACE_ID}/runtimes/probe-runtime/connection-tests"),
@@ -28000,6 +28116,25 @@ mod tests {
.await .await
} }
#[tokio::test]
async fn runtime_connection_verification_requires_workspace_owner() {
let dir = tempfile::tempdir().unwrap();
let api = test_api(dir.path()).await;
let mut actor = test_owner_actor();
actor.account_id = "account-other".to_string();
let error = scoped_test_runtime_connection(
State(api),
AxumPath(ScopedRuntimePath {
workspace_id: TEST_WORKSPACE_ID.to_string(),
runtime_id: "probe-runtime".to_string(),
}),
Extension(actor),
)
.await
.unwrap_err();
assert_eq!(error.into_response().status(), StatusCode::FORBIDDEN);
}
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn runtime_connection_test_reports_exact_compatible_protocol() { async fn runtime_connection_test_reports_exact_compatible_protocol() {
let response = run_runtime_connection_test( let response = run_runtime_connection_test(
@@ -28012,6 +28147,9 @@ mod tests {
.await; .await;
assert_eq!(response["status"], "compatible"); assert_eq!(response["status"], "compatible");
assert_eq!(response["binding_revision"], 1);
assert_eq!(response["connection_state"], "verified");
assert_eq!(response["verification"], serde_json::Value::Null);
assert_eq!(response["failure_kind"], serde_json::Value::Null); assert_eq!(response["failure_kind"], serde_json::Value::Null);
assert_eq!( assert_eq!(
response["expected_protocol_version"], response["expected_protocol_version"],
@@ -30413,8 +30551,14 @@ VALUES ('0192f0e8-4d84-7d6e-a000-000000000001', 'ticket', 3);
) )
.await .await
.unwrap(); .unwrap();
assert_eq!(response.status(), StatusCode::OK, "{uri}"); let status = response.status();
let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap(); let bytes = to_bytes(response.into_body(), usize::MAX).await.unwrap();
assert_eq!(
status,
StatusCode::OK,
"{uri}: {}",
String::from_utf8_lossy(&bytes)
);
serde_json::from_slice(&bytes).unwrap() serde_json::from_slice(&bytes).unwrap()
} }
+67 -10
View File
@@ -2234,6 +2234,7 @@ impl SqliteWorkspaceStore {
return Ok(false); return Ok(false);
}; };
Ok(evidence.state == "verified" Ok(evidence.state == "verified"
&& evidence.last_outcome == "verified"
&& evidence.verified_at.is_some() && evidence.verified_at.is_some()
&& evidence.binding_revision == binding.binding_revision && evidence.binding_revision == binding.binding_revision
&& evidence.workspace_key_id == binding.workspace_key_id.as_deref().unwrap_or_default() && evidence.workspace_key_id == binding.workspace_key_id.as_deref().unwrap_or_default()
@@ -2251,18 +2252,22 @@ impl SqliteWorkspaceStore {
validate_identifier("workspace_id", workspace_id)?; validate_identifier("workspace_id", workspace_id)?;
validate_identifier("runtime_id", runtime_id)?; validate_identifier("runtime_id", runtime_id)?;
self.with_conn(|conn| { self.with_conn(|conn| {
conn.query_row( let evidence = conn
r#"SELECT workspace_id, runtime_id, binding_revision, workspace_key_id, .query_row(
r#"SELECT workspace_id, runtime_id, binding_revision, workspace_key_id,
workspace_identity_revision, workspace_trust_generation, workspace_identity_revision, workspace_trust_generation,
runtime_public_key_fingerprint, runtime_identity_revision, runtime_public_key_fingerprint, runtime_identity_revision,
challenge_id, state, last_outcome, verified_at, checked_at challenge_id, state, last_outcome, verified_at, checked_at
FROM workspace_runtime_verifications FROM workspace_runtime_verifications
WHERE workspace_id = ?1 AND runtime_id = ?2"#, WHERE workspace_id = ?1 AND runtime_id = ?2"#,
params![workspace_id, runtime_id], params![workspace_id, runtime_id],
read_workspace_runtime_verification, read_workspace_runtime_verification,
) )
.optional() .optional()?;
.map_err(Error::from) if let Some(evidence) = &evidence {
validate_workspace_runtime_verification(evidence)?;
}
Ok(evidence)
}) })
} }
@@ -2287,9 +2292,29 @@ impl SqliteWorkspaceStore {
runtime_public_key_fingerprint = excluded.runtime_public_key_fingerprint, runtime_public_key_fingerprint = excluded.runtime_public_key_fingerprint,
runtime_identity_revision = excluded.runtime_identity_revision, runtime_identity_revision = excluded.runtime_identity_revision,
challenge_id = excluded.challenge_id, challenge_id = excluded.challenge_id,
state = excluded.state, state = CASE
WHEN workspace_runtime_verifications.state = 'verified'
AND workspace_runtime_verifications.binding_revision = excluded.binding_revision
AND workspace_runtime_verifications.workspace_key_id = excluded.workspace_key_id
AND workspace_runtime_verifications.workspace_identity_revision = excluded.workspace_identity_revision
AND workspace_runtime_verifications.workspace_trust_generation = excluded.workspace_trust_generation
AND workspace_runtime_verifications.runtime_public_key_fingerprint = excluded.runtime_public_key_fingerprint
AND workspace_runtime_verifications.runtime_identity_revision = excluded.runtime_identity_revision
THEN workspace_runtime_verifications.state
ELSE excluded.state
END,
last_outcome = excluded.last_outcome, last_outcome = excluded.last_outcome,
verified_at = excluded.verified_at, verified_at = CASE
WHEN workspace_runtime_verifications.state = 'verified'
AND workspace_runtime_verifications.binding_revision = excluded.binding_revision
AND workspace_runtime_verifications.workspace_key_id = excluded.workspace_key_id
AND workspace_runtime_verifications.workspace_identity_revision = excluded.workspace_identity_revision
AND workspace_runtime_verifications.workspace_trust_generation = excluded.workspace_trust_generation
AND workspace_runtime_verifications.runtime_public_key_fingerprint = excluded.runtime_public_key_fingerprint
AND workspace_runtime_verifications.runtime_identity_revision = excluded.runtime_identity_revision
THEN workspace_runtime_verifications.verified_at
ELSE excluded.verified_at
END,
checked_at = excluded.checked_at"#, checked_at = excluded.checked_at"#,
params![ params![
evidence.workspace_id, evidence.workspace_id,
@@ -6725,7 +6750,14 @@ fn validate_workspace_runtime_verification(
&evidence.runtime_public_key_fingerprint, &evidence.runtime_public_key_fingerprint,
)?; )?;
validate_non_empty("verification state", &evidence.state)?; validate_non_empty("verification state", &evidence.state)?;
validate_non_empty("verification outcome", &evidence.last_outcome)?; if !matches!(
evidence.last_outcome.as_str(),
"verified" | "challenge_issued" | "verification_failed" | "connectivity_failed"
) {
return Err(Error::InvalidInput(
"Runtime verification outcome is invalid".to_string(),
));
}
validate_non_empty("checked_at", &evidence.checked_at)?; validate_non_empty("checked_at", &evidence.checked_at)?;
Ok(()) Ok(())
} }
@@ -9829,6 +9861,31 @@ mod tests {
.complete_workspace_runtime_verification(&evidence) .complete_workspace_runtime_verification(&evidence)
.unwrap(); .unwrap();
assert_eq!(verified.state, WorkspaceRuntimeBindingState::Verified); assert_eq!(verified.state, WorkspaceRuntimeBindingState::Verified);
let pending_retry = WorkspaceRuntimeVerificationEvidence {
state: "pending".to_string(),
last_outcome: "challenge_issued".to_string(),
verified_at: None,
checked_at: "3".to_string(),
..evidence.clone()
};
store
.record_workspace_runtime_verification_attempt(&pending_retry)
.unwrap();
let retained = store
.get_workspace_runtime_verification("workspace-a", "runtime-a")
.unwrap()
.unwrap();
assert_eq!(retained.state, "verified");
assert_eq!(retained.verified_at.as_deref(), Some("2"));
assert_eq!(retained.last_outcome, "challenge_issued");
assert!(
!store
.workspace_runtime_verification_matches(&verified, 1, 1)
.unwrap()
);
store
.complete_workspace_runtime_verification(&evidence)
.unwrap();
drop(store); drop(store);
let reopened = SqliteWorkspaceStore::open(&path).unwrap(); let reopened = SqliteWorkspaceStore::open(&path).unwrap();
@@ -349,16 +349,42 @@ export type WorkspaceRuntimeBindingState =
| "verified" | "verified"
| "revoked"; | "revoked";
export type RuntimeConnectionDisplayState =
| "configured"
| "verified"
| "unavailable"
| "revoked";
export type RuntimeVerificationOutcome =
| "verified"
| "challenge_issued"
| "verification_failed"
| "connectivity_failed";
export type RuntimeVerificationEvidenceSummary = {
verified_at: string | null;
last_checked_at: string;
last_outcome: RuntimeVerificationOutcome;
binding_revision: number;
workspace_key_id: string;
workspace_identity_revision: number;
workspace_trust_generation: number;
runtime_public_key_fingerprint: string;
runtime_identity_revision: number;
};
export type WorkspaceRuntimeAuthenticationMode = export type WorkspaceRuntimeAuthenticationMode =
| "legacy_server_issuer" | "legacy_server_issuer"
| "workspace_identity"; | "workspace_identity";
export type WorkspaceRuntimeBindingSummary = { export type WorkspaceRuntimeBindingSummary = {
state: WorkspaceRuntimeBindingState; state: WorkspaceRuntimeBindingState;
connection_state: RuntimeConnectionDisplayState;
authentication_mode: WorkspaceRuntimeAuthenticationMode; authentication_mode: WorkspaceRuntimeAuthenticationMode;
revision: number; revision: number;
workspace_key_id?: string | null; workspace_key_id?: string | null;
workspace_key_generation?: number | null; workspace_key_generation?: number | null;
verification?: RuntimeVerificationEvidenceSummary | null;
}; };
export type RuntimeManagementSummary = { export type RuntimeManagementSummary = {
@@ -464,6 +490,9 @@ export type RuntimeConnectionTestFailureKind =
export type RuntimeConnectionTestResponse = { export type RuntimeConnectionTestResponse = {
workspace_id: string; workspace_id: string;
runtime_id: string; runtime_id: string;
binding_revision: number;
connection_state: RuntimeConnectionDisplayState;
verification: RuntimeVerificationEvidenceSummary | null;
checked_at: string; checked_at: string;
status: RuntimeConnectionTestStatus; status: RuntimeConnectionTestStatus;
failure_kind: RuntimeConnectionTestFailureKind | null; failure_kind: RuntimeConnectionTestFailureKind | null;
@@ -2,11 +2,15 @@ import type {
Diagnostic, Diagnostic,
RuntimeConnectionTestFailureKind, RuntimeConnectionTestFailureKind,
RuntimeConnectionTestResponse, RuntimeConnectionTestResponse,
RuntimeVerificationEvidenceSummary,
} from "$lib/generated/workspace-api"; } from "$lib/generated/workspace-api";
const RESPONSE_KEYS = [ const RESPONSE_KEYS = [
"workspace_id", "workspace_id",
"runtime_id", "runtime_id",
"binding_revision",
"connection_state",
"verification",
"checked_at", "checked_at",
"status", "status",
"failure_kind", "failure_kind",
@@ -103,9 +107,28 @@ export function parseRuntimeConnectionTestResponse(
) { ) {
return null; return null;
} }
const bindingRevision = value.binding_revision;
const connectionState = parseConnectionState(value.connection_state);
const verification = parseVerificationEvidence(value.verification);
if (
!isSafeRevision(bindingRevision) ||
connectionState === null ||
(value.verification !== null && verification === null) ||
(verification !== null &&
verification.binding_revision !== bindingRevision) ||
(connectionState === "verified" && value.status !== "compatible") ||
(connectionState === "verified" && verification !== null &&
(verification.last_outcome !== "verified" ||
verification.verified_at === null))
) {
return null;
}
return { return {
workspace_id: value.workspace_id, workspace_id: value.workspace_id,
runtime_id: value.runtime_id, runtime_id: value.runtime_id,
binding_revision: bindingRevision,
connection_state: connectionState,
verification,
checked_at: value.checked_at, checked_at: value.checked_at,
status: value.status, status: value.status,
failure_kind: failureKind as RuntimeConnectionTestFailureKind | null, failure_kind: failureKind as RuntimeConnectionTestFailureKind | null,
@@ -115,6 +138,67 @@ export function parseRuntimeConnectionTestResponse(
}; };
} }
function parseConnectionState(
value: unknown,
): "configured" | "verified" | "unavailable" | "revoked" | null {
return value === "configured" || value === "verified" ||
value === "unavailable" || value === "revoked"
? value
: null;
}
function isSafeRevision(value: unknown): value is number {
return Number.isSafeInteger(value) && (value as number) >= 0;
}
function parseVerificationEvidence(
value: unknown,
): RuntimeVerificationEvidenceSummary | null {
if (value === null) return null;
const keys = [
"verified_at",
"last_checked_at",
"last_outcome",
"binding_revision",
"workspace_key_id",
"workspace_identity_revision",
"workspace_trust_generation",
"runtime_public_key_fingerprint",
"runtime_identity_revision",
] as const;
if (!isRecord(value) || !hasExactKeys(value, keys)) return null;
if (
(value.verified_at !== null &&
(!isBoundedString(value.verified_at, 128) ||
Number.isNaN(Date.parse(value.verified_at)))) ||
!isBoundedString(value.last_checked_at, 128) ||
Number.isNaN(Date.parse(value.last_checked_at)) ||
(value.last_outcome !== "verified" &&
value.last_outcome !== "challenge_issued" &&
value.last_outcome !== "verification_failed" &&
value.last_outcome !== "connectivity_failed") ||
!isSafeRevision(value.binding_revision) ||
!isBoundedString(value.workspace_key_id, 128) ||
!isSafeRevision(value.workspace_identity_revision) ||
!isSafeRevision(value.workspace_trust_generation) ||
!isBoundedString(value.runtime_public_key_fingerprint, 128) ||
!isSafeRevision(value.runtime_identity_revision)
) {
return null;
}
return {
verified_at: value.verified_at,
last_checked_at: value.last_checked_at,
last_outcome: value.last_outcome,
binding_revision: value.binding_revision,
workspace_key_id: value.workspace_key_id,
workspace_identity_revision: value.workspace_identity_revision,
workspace_trust_generation: value.workspace_trust_generation,
runtime_public_key_fingerprint: value.runtime_public_key_fingerprint,
runtime_identity_revision: value.runtime_identity_revision,
};
}
export async function testRuntimeConnection( export async function testRuntimeConnection(
workspaceId: string, workspaceId: string,
runtimeId: string, runtimeId: string,
@@ -3,6 +3,7 @@ import type {
Diagnostic, Diagnostic,
PutRuntimeTrustKeyRequest, PutRuntimeTrustKeyRequest,
RevokeRuntimeTrustKeyRequest, RevokeRuntimeTrustKeyRequest,
RuntimeConnectionDisplayState,
RuntimeIdentityAuthority, RuntimeIdentityAuthority,
RuntimeManagementSummary, RuntimeManagementSummary,
RuntimeSourceKind, RuntimeSourceKind,
@@ -15,6 +16,7 @@ import type {
RuntimeTrustKeyRevealResponse, RuntimeTrustKeyRevealResponse,
RuntimeTrustKeyState, RuntimeTrustKeyState,
RuntimeTrustKeyStatus, RuntimeTrustKeyStatus,
RuntimeVerificationEvidenceSummary,
WorkspaceRuntimeAuthenticationMode, WorkspaceRuntimeAuthenticationMode,
WorkspaceRuntimeBindingState, WorkspaceRuntimeBindingState,
WorkspaceRuntimeBindingSummary, WorkspaceRuntimeBindingSummary,
@@ -76,6 +78,13 @@ const BINDING_STATES = new Set<WorkspaceRuntimeBindingState>([
"verified", "verified",
"revoked", "revoked",
]); ]);
const CONNECTION_STATES = new Set<RuntimeConnectionDisplayState>([
"configured",
"verified",
"unavailable",
"revoked",
]);
const AUTHENTICATION_MODES = new Set<WorkspaceRuntimeAuthenticationMode>([ const AUTHENTICATION_MODES = new Set<WorkspaceRuntimeAuthenticationMode>([
"legacy_server_issuer", "legacy_server_issuer",
"workspace_identity", "workspace_identity",
@@ -299,6 +308,79 @@ function runtimeSource(value: unknown, path: string): RuntimeSourceSummary {
}; };
} }
function runtimeVerification(
value: unknown,
path: string,
): RuntimeVerificationEvidenceSummary {
const item = object(value, path);
exactKeys(
item,
[
"verified_at",
"last_checked_at",
"last_outcome",
"binding_revision",
"workspace_key_id",
"workspace_identity_revision",
"workspace_trust_generation",
"runtime_public_key_fingerprint",
"runtime_identity_revision",
],
[],
path,
);
const verifiedAt = item.verified_at === null
? null
: boundedString(item.verified_at, `${path}.verified_at`, 128);
const lastOutcome = enumValue(
item.last_outcome,
`${path}.last_outcome`,
new Set(
[
"verified",
"challenge_issued",
"verification_failed",
"connectivity_failed",
] as const,
),
);
return {
verified_at: verifiedAt,
last_checked_at: boundedString(
item.last_checked_at,
`${path}.last_checked_at`,
128,
),
last_outcome: lastOutcome,
binding_revision: safeRevision(
item.binding_revision,
`${path}.binding_revision`,
),
workspace_key_id: boundedString(
item.workspace_key_id,
`${path}.workspace_key_id`,
LIMITS.idBytes,
),
workspace_identity_revision: safeRevision(
item.workspace_identity_revision,
`${path}.workspace_identity_revision`,
),
workspace_trust_generation: safeRevision(
item.workspace_trust_generation,
`${path}.workspace_trust_generation`,
),
runtime_public_key_fingerprint: boundedString(
item.runtime_public_key_fingerprint,
`${path}.runtime_public_key_fingerprint`,
LIMITS.fingerprintBytes,
),
runtime_identity_revision: safeRevision(
item.runtime_identity_revision,
`${path}.runtime_identity_revision`,
),
};
}
function runtimeBinding( function runtimeBinding(
value: unknown, value: unknown,
path: string, path: string,
@@ -306,8 +388,8 @@ function runtimeBinding(
const item = object(value, path); const item = object(value, path);
exactKeys( exactKeys(
item, item,
["state", "authentication_mode", "revision"], ["state", "connection_state", "authentication_mode", "revision"],
["workspace_key_id", "workspace_key_generation"], ["workspace_key_id", "workspace_key_generation", "verification"],
path, path,
); );
const authenticationMode = enumValue( const authenticationMode = enumValue(
@@ -339,16 +421,45 @@ function runtimeBinding(
"must not attach Workspace key metadata to legacy authority", "must not attach Workspace key metadata to legacy authority",
); );
} }
const state = enumValue(item.state, `${path}.state`, BINDING_STATES);
const connectionState = enumValue(
item.connection_state,
`${path}.connection_state`,
CONNECTION_STATES,
);
const revision = safeRevision(item.revision, `${path}.revision`);
const verification = item.verification === undefined
? undefined
: runtimeVerification(item.verification, `${path}.verification`);
if (
verification !== undefined && verification.binding_revision !== revision
) {
return fail(path, "verification must match the current binding revision");
}
if (
authenticationMode === "workspace_identity" &&
connectionState === "verified" &&
(verification === undefined ||
verification.verified_at === null ||
verification.last_outcome !== "verified")
) {
return fail(
path,
"verified Workspace identity binding requires verification evidence",
);
}
return { return {
state: enumValue(item.state, `${path}.state`, BINDING_STATES), state,
connection_state: connectionState,
authentication_mode: authenticationMode, authentication_mode: authenticationMode,
revision: safeRevision(item.revision, `${path}.revision`), revision,
...(workspaceKeyId === undefined ...(workspaceKeyId === undefined
? {} ? {}
: { workspace_key_id: workspaceKeyId }), : { workspace_key_id: workspaceKeyId }),
...(workspaceKeyGeneration === undefined ...(workspaceKeyGeneration === undefined
? {} ? {}
: { workspace_key_generation: workspaceKeyGeneration }), : { workspace_key_generation: workspaceKeyGeneration }),
...(verification === undefined ? {} : { verification }),
}; };
} }
@@ -26,6 +26,7 @@
let busyRuntimeId = $state<string | null>(null); let busyRuntimeId = $state<string | null>(null);
let requestError = $state<string | null>(null); let requestError = $state<string | null>(null);
let testResults = $state<Record<string, RuntimeConnectionTestResponse>>({}); let testResults = $state<Record<string, RuntimeConnectionTestResponse>>({});
let connectionTestGeneration = 0;
function runtimePlatform(runtime: WorkspaceRuntimeResource): string { function runtimePlatform(runtime: WorkspaceRuntimeResource): string {
return runtime.os && runtime.arch ? `${runtime.os} / ${runtime.arch}` : 'Unknown'; return runtime.os && runtime.arch ? `${runtime.os} / ${runtime.arch}` : 'Unknown';
@@ -33,7 +34,9 @@
function connectionTestSummary(result: RuntimeConnectionTestResponse): string { function connectionTestSummary(result: RuntimeConnectionTestResponse): string {
if (result.status === 'compatible') { if (result.status === 'compatible') {
return `Compatible · protocol v${result.actual_protocol_version}`; return result.connection_state === 'verified'
? `Verified · protocol v${result.actual_protocol_version}`
: `Compatible · ${result.connection_state} · protocol v${result.actual_protocol_version}`;
} }
switch (result.failure_kind) { switch (result.failure_kind) {
case 'authentication': return 'Authentication failed'; case 'authentication': return 'Authentication failed';
@@ -144,16 +147,40 @@
} }
} }
function currentTestResult(
runtime: WorkspaceRuntimeResource,
): RuntimeConnectionTestResponse | undefined {
const result = testResults[runtime.runtime_id];
return result?.binding_revision === runtime.management?.binding?.revision
? result
: undefined;
}
async function testRuntime(runtime: WorkspaceRuntimeResource): Promise<void> { async function testRuntime(runtime: WorkspaceRuntimeResource): Promise<void> {
const bindingRevision = runtime.management?.binding?.revision;
if (typeof bindingRevision !== 'number') return;
const generation = ++connectionTestGeneration;
requestError = null; requestError = null;
busyRuntimeId = runtime.runtime_id; busyRuntimeId = runtime.runtime_id;
try { try {
const result = await testRuntimeConnection(data.workspaceId, runtime.runtime_id); const result = await testRuntimeConnection(data.workspaceId, runtime.runtime_id);
if (
generation !== connectionTestGeneration ||
result.binding_revision !== bindingRevision
) {
await invalidateAll();
return;
}
testResults = { ...testResults, [runtime.runtime_id]: result }; testResults = { ...testResults, [runtime.runtime_id]: result };
await invalidateAll();
} catch (error) { } catch (error) {
requestError = error instanceof Error ? error.message : String(error); if (generation === connectionTestGeneration) {
requestError = error instanceof Error ? error.message : String(error);
}
} finally { } finally {
busyRuntimeId = null; if (generation === connectionTestGeneration) {
busyRuntimeId = null;
}
} }
} }
</script> </script>
@@ -287,7 +314,7 @@
</td> </td>
<td>{runtime.kind}</td> <td>{runtime.kind}</td>
<td> <td>
{runtime.management?.binding?.state ?? runtime.status} {runtime.management?.binding?.connection_state ?? runtime.status}
</td> </td>
<td>{runtimePlatform(runtime)}</td> <td>{runtimePlatform(runtime)}</td>
<td>{managementLabel(runtime)}</td> <td>{managementLabel(runtime)}</td>
@@ -298,7 +325,7 @@
</td> </td>
<td> <td>
<div class="settings-action-row"> <div class="settings-action-row">
{#if runtime.management?.config_managed && runtime.management.binding?.state === 'verified'} {#if runtime.management?.config_managed && runtime.management.binding?.connection_state !== 'revoked'}
<button <button
type="button" type="button"
disabled={busyRuntimeId !== null} disabled={busyRuntimeId !== null}
@@ -313,7 +340,8 @@
</div> </div>
</td> </td>
</tr> </tr>
{#if runtime.diagnostics.length > 0 || testResults[runtime.runtime_id]} {@const currentResult = currentTestResult(runtime)}
{#if runtime.diagnostics.length > 0 || currentResult}
<tr class="settings-runtime-detail-row"> <tr class="settings-runtime-detail-row">
<td colspan="7"> <td colspan="7">
{#if runtime.diagnostics.length > 0} {#if runtime.diagnostics.length > 0}
@@ -326,14 +354,13 @@
{/each} {/each}
</ul> </ul>
{/if} {/if}
{#if testResults[runtime.runtime_id]} {#if currentResult}
{@const result = testResults[runtime.runtime_id]} <div class:failed={currentResult.status === 'failed'} class="settings-test-result">
<div class:failed={result.status === 'failed'} class="settings-test-result"> <strong>Connection test: {connectionTestSummary(currentResult)}</strong>
<strong>Connection test: {connectionTestSummary(result)}</strong> {#if currentResult.diagnostics[0]}
{#if result.diagnostics[0]} <span>{currentResult.diagnostics[0].message}</span>
<span>{result.diagnostics[0].message}</span>
{/if} {/if}
<small>Checked {new Date(result.checked_at).toLocaleString()}</small> <small>Checked {new Date(currentResult.checked_at).toLocaleString()}</small>
</div> </div>
{/if} {/if}
</td> </td>
@@ -332,9 +332,12 @@
<div><dt>Kind</dt><dd>{runtime.kind}</dd></div> <div><dt>Kind</dt><dd>{runtime.kind}</dd></div>
<div><dt>Endpoint</dt><dd>{detail.endpoint ?? 'Not configured'}</dd></div> <div><dt>Endpoint</dt><dd>{detail.endpoint ?? 'Not configured'}</dd></div>
<div><dt>Status</dt><dd>{runtime.status}</dd></div> <div><dt>Status</dt><dd>{runtime.status}</dd></div>
<div><dt>Relationship state</dt><dd>{runtime.management.binding?.state ?? 'Not configured'}</dd></div> <div><dt>Connection state</dt><dd>{runtime.management.binding?.connection_state ?? 'Not configured'}</dd></div>
<div><dt>Authentication mode</dt><dd>{runtime.management.binding?.authentication_mode ?? '—'}</dd></div> <div><dt>Authentication mode</dt><dd>{runtime.management.binding?.authentication_mode ?? '—'}</dd></div>
<div><dt>Workspace signing key</dt><dd><code>{runtime.management.binding?.workspace_key_id ?? '—'}</code></dd></div> <div><dt>Workspace signing key</dt><dd><code>{runtime.management.binding?.workspace_key_id ?? '—'}</code></dd></div>
<div><dt>Verified</dt><dd>{formatTimestamp(runtime.management.binding?.verification?.verified_at)}</dd></div>
<div><dt>Verified binding revision</dt><dd>{runtime.management.binding?.verification?.binding_revision?.toString() ?? '—'}</dd></div>
<div><dt>Last verification check</dt><dd>{runtime.management.binding?.verification?.last_outcome ?? '—'} · {formatTimestamp(runtime.management.binding?.verification?.last_checked_at)}</dd></div>
<div><dt>Runtime key status</dt><dd>{trust.status}</dd></div> <div><dt>Runtime key status</dt><dd>{trust.status}</dd></div>
<div><dt>Fingerprint</dt><dd><code>{trust.fingerprint ?? '—'}</code></dd></div> <div><dt>Fingerprint</dt><dd><code>{trust.fingerprint ?? '—'}</code></dd></div>
<div><dt>Revision</dt><dd>{trust.revision?.toString() ?? '—'}</dd></div> <div><dt>Revision</dt><dd>{trust.revision?.toString() ?? '—'}</dd></div>
@@ -19,6 +19,9 @@ function compatibleResponse(): Record<string, unknown> {
return { return {
workspace_id: "workspace-a", workspace_id: "workspace-a",
runtime_id: "runtime-a", runtime_id: "runtime-a",
binding_revision: 3,
connection_state: "verified",
verification: null,
checked_at: "2026-09-01T12:00:00Z", checked_at: "2026-09-01T12:00:00Z",
status: "compatible", status: "compatible",
failure_kind: null, failure_kind: null,
@@ -57,6 +60,23 @@ Deno.test("runtime connection response rejects unknown fields and incoherent com
}), }),
null, null,
); );
assertEquals(
parseRuntimeConnectionTestResponse({
...compatibleResponse(),
verification: {
verified_at: "2026-09-01T12:00:00Z",
last_checked_at: "2026-09-01T12:00:01Z",
last_outcome: "verified",
binding_revision: 2,
workspace_key_id: "WK-a",
workspace_identity_revision: 1,
workspace_trust_generation: 1,
runtime_public_key_fingerprint: "sha256:runtime",
runtime_identity_revision: 1,
},
}),
null,
);
}); });
Deno.test("runtime connection response rejects unknown failure kinds and unbounded diagnostics", () => { Deno.test("runtime connection response rejects unknown failure kinds and unbounded diagnostics", () => {
@@ -42,10 +42,22 @@ function runtime() {
token_ref_configured: false, token_ref_configured: false,
binding: { binding: {
state: "verified", state: "verified",
connection_state: "verified",
authentication_mode: "workspace_identity", authentication_mode: "workspace_identity",
revision: 3, revision: 3,
workspace_key_id: "WK-1", workspace_key_id: "WK-1",
workspace_key_generation: 1, workspace_key_generation: 1,
verification: {
verified_at: "2026-09-01T13:00:00Z",
last_checked_at: "2026-09-01T13:00:00Z",
last_outcome: "verified",
binding_revision: 3,
workspace_key_id: "WK-1",
workspace_identity_revision: 1,
workspace_trust_generation: 1,
runtime_public_key_fingerprint: "SHA256:current",
runtime_identity_revision: 1,
},
}, },
}, },
runtime_id: "arcadia", runtime_id: "arcadia",