+ where
+ R: Send + 'static,
+ E: Send + 'static,
+ {
+ match self.transact(body).await.map_err(wrap)? {
+ Ok(value) | Err(JobExit::Rollback(value)) => Ok(value),
+ Err(JobExit::Fail(error)) => Err(error),
+ }
+ }
+
pub fn binding(&self) -> &tracedecay_store::StoreRuntimeBindingV1 {
self.database.registered_binding()
}
@@ -866,27 +902,6 @@ impl tracedecay_sessions::runtime::git_correlation::GitCorrelationWriteTxn
}
}
-impl tracedecay_sessions::runtime::workflow_index::WorkflowIngestWriteTxn
- for RegisteredGlobalDbWriteTransaction<'_>
-{
- #[allow(clippy::manual_async_fn)]
- fn commit(
- self,
- ) -> impl Future<
- Output = Result<(), tracedecay_sessions::runtime::workflow_index::WorkflowIndexError>,
- > + Send {
- async move {
- RegisteredGlobalDbWriteTransaction::commit(self)
- .await
- .map_err(|error| {
- tracedecay_sessions::runtime::workflow_index::WorkflowIndexError::Db(
- error.to_string(),
- )
- })
- }
- }
-}
-
impl RegisteredGlobalDbWriteTransaction<'_> {
pub async fn execute(
&self,
@@ -955,6 +970,20 @@ impl RegisteredGlobalDbWriteTransaction<'_> {
}
}
+/// The error side of a job body that can end without committing. `Rollback`
+/// carries a value for the caller, so a replay can be reported as a success
+/// without leaving a commit behind; `Fail` is an ordinary failure.
+pub(crate) enum JobExit {
+ Rollback(R),
+ Fail(E),
+}
+
+impl From for JobExit {
+ fn from(error: E) -> Self {
+ Self::Fail(error)
+ }
+}
+
/// The transaction a [`RegisteredGlobalDb::transact`] body runs in. It is a
/// borrowed handle, so the body takes it by value and lends `&transaction` to
/// helpers written against the executor traits.
diff --git a/crates/tracedecay-global-db/src/registry_maintenance.rs b/crates/tracedecay-global-db/src/registry_maintenance.rs
index fb474b5a1e..a8f82c7e9a 100644
--- a/crates/tracedecay-global-db/src/registry_maintenance.rs
+++ b/crates/tracedecay-global-db/src/registry_maintenance.rs
@@ -6,7 +6,7 @@ use serde::Serialize;
use crate::{
CodeProjectRecord, GraphScopeUpsert, ProjectRegistryContext, RegisteredGlobalDb,
- RegisteredGlobalDbWriteTransaction, StoreArtifactUpsert, StoreInstanceUpsert,
+ StoreArtifactUpsert, StoreInstanceUpsert,
};
use tracedecay_runtime_core::branch_meta;
use tracedecay_runtime_core::storage::{
diff --git a/crates/tracedecay-sessions/src/runtime/workflow/workflow_index.rs b/crates/tracedecay-sessions/src/runtime/workflow/workflow_index.rs
index 17cfda8d2a..0ca3ddc53f 100644
--- a/crates/tracedecay-sessions/src/runtime/workflow/workflow_index.rs
+++ b/crates/tracedecay-sessions/src/runtime/workflow/workflow_index.rs
@@ -698,7 +698,7 @@ async fn runs_for_git_scope(
}
mod port;
-pub use port::{WorkflowIngestSink, WorkflowIngestWriteTxn};
+pub use port::WorkflowIngestSink;
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
diff --git a/crates/tracedecay-sessions/src/runtime/workflow/workflow_index/port.rs b/crates/tracedecay-sessions/src/runtime/workflow/workflow_index/port.rs
index 782d744a1e..8dd90aee1c 100644
--- a/crates/tracedecay-sessions/src/runtime/workflow/workflow_index/port.rs
+++ b/crates/tracedecay-sessions/src/runtime/workflow/workflow_index/port.rs
@@ -6,15 +6,9 @@
use std::future::Future;
-use tracedecay_runtime_core::db::engine::{Executor, QueryExecutor};
-
use super::WorkflowIndexError;
use crate::{WorkflowAgent, WorkflowRun};
-pub trait WorkflowIngestWriteTxn: QueryExecutor + Executor + Sized + Send {
- fn commit(self) -> impl Future> + Send;
-}
-
/// Authoritative sink one workflow-ingest sweep upserts through.
///
/// The sweep needs an authority check, a watermark it can advance, and a
From 156af12380ec8be06ca8169884d1ad478c91a782 Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 16:14:12 +0000
Subject: [PATCH 08/89] refactor(global-db): run accounting and workflow writes
as jobs
---
.../src/registered_accounting.rs | 72 ++++++++++---------
.../src/workflow_adapter.rs | 60 ++++++++--------
2 files changed, 68 insertions(+), 64 deletions(-)
diff --git a/crates/tracedecay-global-db/src/registered_accounting.rs b/crates/tracedecay-global-db/src/registered_accounting.rs
index 9d7895488a..4b056d7147 100644
--- a/crates/tracedecay-global-db/src/registered_accounting.rs
+++ b/crates/tracedecay-global-db/src/registered_accounting.rs
@@ -1,6 +1,6 @@
use std::path::Path;
-use tracedecay_runtime_core::db::engine::Value;
+use tracedecay_runtime_core::db::engine::{Executor as _, Value};
use super::{
RegisteredGlobalDb, SavingsDay, SavingsTotal, global_db_operation_error,
@@ -19,20 +19,22 @@ impl RegisteredGlobalDb {
tokens_saved: u64,
) -> tracedecay_domain::errors::Result<()> {
let path = super::project_path_alias_key(project_path);
- let transaction = self.begin_write_transaction().await?;
- transaction
- .execute(
- "INSERT INTO projects (path, tokens_saved) VALUES (?1, ?2)
- ON CONFLICT(path) DO UPDATE SET
- tokens_saved = MAX(tokens_saved, excluded.tokens_saved)",
- tracedecay_runtime_core::db::engine::params![path, tokens_saved as i64],
- )
- .await
- .map_err(|error| global_db_operation_error("update project token total", error))?;
- transaction
- .commit()
- .await
- .map_err(|error| global_db_operation_error("commit project token total", error))
+ self.transact(async move |transaction| {
+ transaction
+ .execute(
+ "INSERT INTO projects (path, tokens_saved) VALUES (?1, ?2)
+ ON CONFLICT(path) DO UPDATE SET
+ tokens_saved = MAX(tokens_saved, excluded.tokens_saved)",
+ tracedecay_runtime_core::db::engine::params![
+ path.as_str(),
+ tokens_saved as i64
+ ],
+ )
+ .await
+ .map_err(|error| global_db_operation_error("update project token total", error))?;
+ Ok(())
+ })
+ .await?
}
/// A project with no registry row has genuinely saved nothing, so an
@@ -87,26 +89,26 @@ impl RegisteredGlobalDb {
timestamp: i64,
) -> tracedecay_domain::errors::Result<()> {
let project_path = RegisteredGlobalDb::canonical_project_key(Path::new(project_path));
- let transaction = self.begin_write_transaction().await?;
- transaction
- .execute(
- "INSERT INTO savings_ledger
- (ts, project_path, tool_name, before_tokens, after_tokens)
- VALUES (?1, ?2, ?3, ?4, ?5)",
- tracedecay_runtime_core::db::engine::params![
- timestamp,
- project_path,
- tool_name,
- before_tokens as i64,
- after_tokens as i64
- ],
- )
- .await
- .map_err(|error| global_db_operation_error("append savings ledger entry", error))?;
- transaction
- .commit()
- .await
- .map_err(|error| global_db_operation_error("commit savings ledger entry", error))
+ let tool_name = tool_name.to_owned();
+ self.transact(async move |transaction| {
+ transaction
+ .execute(
+ "INSERT INTO savings_ledger
+ (ts, project_path, tool_name, before_tokens, after_tokens)
+ VALUES (?1, ?2, ?3, ?4, ?5)",
+ tracedecay_runtime_core::db::engine::params![
+ timestamp,
+ project_path.as_str(),
+ tool_name.as_str(),
+ before_tokens as i64,
+ after_tokens as i64
+ ],
+ )
+ .await
+ .map_err(|error| global_db_operation_error("append savings ledger entry", error))?;
+ Ok(())
+ })
+ .await?
}
/// Sums settled savings-ledger rows for an optional project path.
diff --git a/crates/tracedecay-global-db/src/workflow_adapter.rs b/crates/tracedecay-global-db/src/workflow_adapter.rs
index 0dc375dbc5..bd14d14813 100644
--- a/crates/tracedecay-global-db/src/workflow_adapter.rs
+++ b/crates/tracedecay-global-db/src/workflow_adapter.rs
@@ -7,14 +7,13 @@ use std::path::Path;
use tracedecay_domain::ProjectId;
use tracedecay_store::StoreShardScopeV1;
-use crate::{RegisteredGlobalDb, RegisteredGlobalDbWriteTransaction};
-use tracedecay_runtime_core::db::engine::params;
+use crate::RegisteredGlobalDb;
+use tracedecay_runtime_core::db::engine::{Executor as _, params};
#[cfg(any(test, feature = "test-helpers"))]
use tracedecay_sessions::runtime::source::TranscriptIngestResult;
use tracedecay_sessions::runtime::workflow_index::{
INGEST_WATERMARK_KEY, RegisteredWorkflowIndexSnapshot, WorkflowAgent, WorkflowIndexError,
- WorkflowIngestSink, WorkflowIngestWriteTxn, WorkflowRun, read_ingest_watermark, upsert_agent,
- upsert_run,
+ WorkflowIngestSink, WorkflowRun, read_ingest_watermark, upsert_agent, upsert_run,
};
#[cfg(any(test, feature = "test-helpers"))]
use tracedecay_sessions::runtime::workflow_ingest::{
@@ -118,26 +117,27 @@ where
#[tracing::instrument(name = "global_db.workflow.bump_watermark", level = "trace", skip_all)]
async fn bump_ingest_watermark(&self, value: i64) {
- let Ok(transaction) = self.db().begin_write_transaction().await else {
- tracing::debug!("workflow ingest writer unavailable");
- return;
- };
- let write = async {
- transaction
- .execute(
- "INSERT INTO workflow_index_meta(key, value)
+ let write = self
+ .db()
+ .transact(async move |transaction| {
+ transaction
+ .execute(
+ "INSERT INTO workflow_index_meta(key, value)
VALUES (?1, ?2)
ON CONFLICT(key) DO UPDATE SET
value = MAX(value, excluded.value),
updated_at = unixepoch()",
- params![INGEST_WATERMARK_KEY, value],
- )
- .await?;
- RegisteredGlobalDbWriteTransaction::commit(transaction).await
- }
- .await;
- if let Err(err) = write {
- tracing::debug!(error = %err, "workflow ingest watermark not advanced");
+ params![INGEST_WATERMARK_KEY, value],
+ )
+ .await
+ })
+ .await;
+ match write {
+ Ok(Ok(_)) => {}
+ Ok(Err(err)) => {
+ tracing::debug!(error = %err, "workflow ingest watermark not advanced");
+ }
+ Err(err) => tracing::debug!(error = %err, "workflow ingest writer unavailable"),
}
}
@@ -147,15 +147,17 @@ where
run: &WorkflowRun,
agents: &[WorkflowAgent],
) -> Result<(), WorkflowIndexError> {
- let transaction = self
- .db()
- .begin_write_transaction()
+ let run = run.clone();
+ let agents = agents.to_vec();
+ self.db()
+ .transact(async move |transaction| {
+ upsert_run(&transaction, &run).await?;
+ for agent in &agents {
+ upsert_agent(&transaction, agent).await?;
+ }
+ Ok(())
+ })
.await
- .map_err(|error| WorkflowIndexError::Db(error.to_string()))?;
- upsert_run(&transaction, run).await?;
- for agent in agents {
- upsert_agent(&transaction, agent).await?;
- }
- WorkflowIngestWriteTxn::commit(transaction).await
+ .map_err(|error| WorkflowIndexError::Db(error.to_string()))?
}
}
From 9111a8fb3f587ca2bf45d547a60d6203947fec96 Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 16:14:13 +0000
Subject: [PATCH 09/89] refactor(global-db): run topology anchor and discovery
writes as jobs
---
.../src/discovery_queue.rs | 143 +++++++++---------
.../src/git_topology_anchor.rs | 83 +++++-----
2 files changed, 109 insertions(+), 117 deletions(-)
diff --git a/crates/tracedecay-global-db/src/discovery_queue.rs b/crates/tracedecay-global-db/src/discovery_queue.rs
index abe0d86ace..c8bc387903 100644
--- a/crates/tracedecay-global-db/src/discovery_queue.rs
+++ b/crates/tracedecay-global-db/src/discovery_queue.rs
@@ -7,7 +7,7 @@
use std::path::{Path, PathBuf};
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
-use tracedecay_runtime_core::db::engine::params;
+use tracedecay_runtime_core::db::engine::{Executor as _, QueryExecutor as _, params};
use crate::RegisteredGlobalDb;
@@ -133,78 +133,77 @@ impl RegisteredGlobalDb {
let Some(last_path) = paths.last().cloned() else {
return Ok(None);
};
- let transaction = self
- .begin_write_transaction()
- .await
- .map_err(|error| format!("begin host discovery queue transaction: {error}"))?;
let (range_start, range_end) = exclusive_prefix_range(&prefix)?;
- let mut sequence_rows = transaction
- .query(
- "SELECT COALESCE(MAX(byte_offset), 0)
- FROM parse_offsets
- WHERE file_path >= ?1 AND file_path < ?2 AND file_id = 1",
- params![range_start, range_end],
- )
- .await
- .map_err(|error| format!("read host discovery queue tail: {error}"))?;
- let sequence_row = sequence_rows
- .next()
- .await
- .map_err(|error| format!("read host discovery queue tail: {error}"))?
- .ok_or_else(|| "host discovery queue tail is unavailable".to_owned())?;
- let mut sequence = sequence_row
- .get::(0)
- .map_err(|error| format!("decode host discovery queue tail: {error}"))?;
- if sequence < 0 {
- return Err("host discovery queue tail is negative".to_owned());
- }
- drop(sequence_rows);
- for path in paths {
- let key = queue_key(&prefix, &path);
- let proposed = sequence
- .checked_add(1)
- .ok_or_else(|| "host discovery queue sequence is exhausted".to_owned())?;
- let inserted = transaction
- .execute(
- "INSERT INTO parse_offsets (file_path, byte_offset, mtime, file_id)
- VALUES (?1, ?2, 0, 1)
- ON CONFLICT(file_path) DO NOTHING",
- params![key, proposed],
- )
- .await
- .map_err(|error| format!("enqueue host discovery path: {error}"))?;
- if inserted > 0 {
- sequence = proposed;
- }
- }
- let last_key = queue_key(&prefix, &last_path);
- let mut rows = transaction
- .query(
- "SELECT byte_offset, file_path
- FROM parse_offsets
- WHERE file_path = ?1 AND file_id = 1",
- params![last_key],
- )
- .await
- .map_err(|error| format!("resolve host discovery queue identity: {error}"))?;
- let row = rows
- .next()
- .await
- .map_err(|error| format!("read host discovery queue identity: {error}"))?
- .ok_or_else(|| "enqueued host discovery path is unavailable".to_owned())?;
- let entry = decode_entry(
- &prefix,
- row.get::(0)
- .map_err(|error| format!("decode host discovery queue sequence: {error}"))?,
- row.get::(1)
- .map_err(|error| format!("decode host discovery queue key: {error}"))?,
- )?;
- drop(rows);
- transaction
- .commit()
- .await
- .map_err(|error| format!("commit host discovery queue: {error}"))?;
- Ok(Some(entry))
+ self.transact_or(
+ |error| format!("run host discovery queue transaction: {error}"),
+ async move |transaction| {
+ let mut sequence_rows = transaction
+ .query(
+ "SELECT COALESCE(MAX(byte_offset), 0)
+ FROM parse_offsets
+ WHERE file_path >= ?1 AND file_path < ?2 AND file_id = 1",
+ params![range_start.as_str(), range_end.as_str()],
+ )
+ .await
+ .map_err(|error| format!("read host discovery queue tail: {error}"))?;
+ let sequence_row = sequence_rows
+ .next()
+ .await
+ .map_err(|error| format!("read host discovery queue tail: {error}"))?
+ .ok_or_else(|| "host discovery queue tail is unavailable".to_owned())?;
+ let mut sequence = sequence_row
+ .get::(0)
+ .map_err(|error| format!("decode host discovery queue tail: {error}"))?;
+ if sequence < 0 {
+ return Err("host discovery queue tail is negative".to_owned());
+ }
+ drop(sequence_rows);
+ for path in &paths {
+ let key = queue_key(&prefix, path);
+ let proposed = sequence
+ .checked_add(1)
+ .ok_or_else(|| "host discovery queue sequence is exhausted".to_owned())?;
+ let inserted = transaction
+ .execute(
+ "INSERT INTO parse_offsets (file_path, byte_offset, mtime, file_id)
+ VALUES (?1, ?2, 0, 1)
+ ON CONFLICT(file_path) DO NOTHING",
+ params![key, proposed],
+ )
+ .await
+ .map_err(|error| format!("enqueue host discovery path: {error}"))?;
+ if inserted > 0 {
+ sequence = proposed;
+ }
+ }
+ let last_key = queue_key(&prefix, &last_path);
+ let mut rows = transaction
+ .query(
+ "SELECT byte_offset, file_path
+ FROM parse_offsets
+ WHERE file_path = ?1 AND file_id = 1",
+ params![last_key],
+ )
+ .await
+ .map_err(|error| format!("resolve host discovery queue identity: {error}"))?;
+ let row = rows
+ .next()
+ .await
+ .map_err(|error| format!("read host discovery queue identity: {error}"))?
+ .ok_or_else(|| "enqueued host discovery path is unavailable".to_owned())?;
+ let entry = decode_entry(
+ &prefix,
+ row.get::(0).map_err(|error| {
+ format!("decode host discovery queue sequence: {error}")
+ })?,
+ row.get::(1)
+ .map_err(|error| format!("decode host discovery queue key: {error}"))?,
+ )?;
+ drop(rows);
+ Ok(Some(entry))
+ },
+ )
+ .await
}
#[tracing::instrument(name = "global_db.discovery_queue.query", level = "trace", skip_all)]
diff --git a/crates/tracedecay-global-db/src/git_topology_anchor.rs b/crates/tracedecay-global-db/src/git_topology_anchor.rs
index 9875b44b8c..88b5b79060 100644
--- a/crates/tracedecay-global-db/src/git_topology_anchor.rs
+++ b/crates/tracedecay-global-db/src/git_topology_anchor.rs
@@ -8,7 +8,7 @@ use tracedecay_contracts::retrieval::{
GitTopologyAnchorResolutionOutcome,
};
use tracedecay_domain::{ObservationScopeV1, RetrievalAnchorRecord, RetrievalAnchorTarget};
-use tracedecay_runtime_core::db::engine::{params, params_from_iter};
+use tracedecay_runtime_core::db::engine::{Executor as _, params, params_from_iter};
use tracedecay_store::StoreShardScopeV1;
use crate::{RegisteredGlobalDb, RegisteredGlobalDbLeaseV1};
@@ -30,58 +30,51 @@ impl RegisteredGitTopologyAnchorAuthority {
if !binding_matches_owner(&self.database, publication.owner()) {
return Err(GitTopologyAnchorAuthorityError::Unavailable);
}
- let transaction = self
- .database
- .begin_write_transaction()
- .await
- .map_err(map_database_error)?;
let records = publication.into_records();
let anchor_ids = records
.iter()
.map(|record| record.anchor_id().as_str().to_owned())
.collect::>();
- let existing = read_records(&transaction, &anchor_ids).await?;
- let mut published = false;
- for candidate in records {
- let existing = existing.get(candidate.anchor_id().as_str());
- match existing {
- Some(existing) if existing.is_semantic_replay_of(&candidate) => continue,
- Some(_) => {
+ self.database
+ .transact(async move |transaction| {
+ let existing = read_records(&transaction, &anchor_ids).await?;
+ let mut published = false;
+ for candidate in &records {
+ let existing = existing.get(candidate.anchor_id().as_str());
+ match existing {
+ Some(existing) if existing.is_semantic_replay_of(candidate) => continue,
+ Some(_) => return Err(GitTopologyAnchorAuthorityError::Conflict),
+ None => {}
+ }
+ let anchor_json = serde_json::to_string(candidate)
+ .map_err(|_| GitTopologyAnchorAuthorityError::Conflict)?;
+ let owner_json = candidate
+ .owner_column_json()
+ .map_err(|_| GitTopologyAnchorAuthorityError::Conflict)?;
transaction
- .rollback()
+ .execute(
+ "INSERT INTO retrieval_anchors (
+ anchor_id, anchor_json, owner_json, projection_generation
+ ) VALUES (?1, ?2, ?3, ?4)",
+ params![
+ candidate.anchor_id().as_str(),
+ anchor_json,
+ owner_json,
+ candidate.projection_generation().as_str(),
+ ],
+ )
.await
- .map_err(|_| GitTopologyAnchorAuthorityError::Unavailable)?;
- return Err(GitTopologyAnchorAuthorityError::Conflict);
+ .map_err(map_engine_error)?;
+ published = true;
}
- None => {}
- }
- let anchor_json = serde_json::to_string(&candidate)
- .map_err(|_| GitTopologyAnchorAuthorityError::Conflict)?;
- let owner_json = candidate
- .owner_column_json()
- .map_err(|_| GitTopologyAnchorAuthorityError::Conflict)?;
- transaction
- .execute(
- "INSERT INTO retrieval_anchors (
- anchor_id, anchor_json, owner_json, projection_generation
- ) VALUES (?1, ?2, ?3, ?4)",
- params![
- candidate.anchor_id().as_str(),
- anchor_json,
- owner_json,
- candidate.projection_generation().as_str(),
- ],
- )
- .await
- .map_err(map_engine_error)?;
- published = true;
- }
- transaction.commit().await.map_err(map_engine_error)?;
- Ok(if published {
- GitTopologyAnchorPublicationOutcome::Published
- } else {
- GitTopologyAnchorPublicationOutcome::Replayed
- })
+ Ok(if published {
+ GitTopologyAnchorPublicationOutcome::Published
+ } else {
+ GitTopologyAnchorPublicationOutcome::Replayed
+ })
+ })
+ .await
+ .map_err(map_database_error)?
}
async fn resolve_record(
From cca8a2f5bf4059c90c883a2f0eb20e40a9212df7 Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 16:14:14 +0000
Subject: [PATCH 10/89] refactor(global-db): run stack delivery writes as jobs
---
.../src/registered/delivery_settlement.rs | 239 +++++-----
.../src/stack_delivery.rs | 448 +++++++++---------
2 files changed, 352 insertions(+), 335 deletions(-)
diff --git a/crates/tracedecay-global-db/src/registered/delivery_settlement.rs b/crates/tracedecay-global-db/src/registered/delivery_settlement.rs
index 3eec135196..1ac55c2bcd 100644
--- a/crates/tracedecay-global-db/src/registered/delivery_settlement.rs
+++ b/crates/tracedecay-global-db/src/registered/delivery_settlement.rs
@@ -1,9 +1,10 @@
-use super::{RegisteredGlobalDb, RegisteredGlobalDbWriteTransaction};
+use super::RegisteredGlobalDb;
use tracedecay_domain::{
CoverageStateV1, DeliveryDropReasonV1, DeliveryEventClassV1, DeliverySettlementAttemptV1,
DeliverySettlementCensusV1, DeliverySettlementOutcomeV1, DeliverySettlementV1,
DeliverySurfaceFamilyV1, UtcMicros, WorkAttemptIdentityV1, canonical_sha256,
};
+use tracedecay_runtime_core::db::engine::{Executor, QueryExecutor};
/// One Work attempt can have at most this many separately addressed delivery
/// fan-outs. The recipient bound is enforced per fan-out by the domain; this
@@ -151,59 +152,62 @@ impl RegisteredGlobalDb {
source_receipt_ref: Option<&str>,
) -> Result {
validate_project_and_attempt(project_id, attempt)?;
- let transaction = self
- .begin_write_transaction()
- .await
- .map_err(|error| format!("failed to begin delivery attempt transaction: {error}"))?;
- ensure_fanout_identity(&transaction, project_id, attempt).await?;
+ let project_id = project_id.to_owned();
+ let attempt = attempt.clone();
+ let source_receipt_ref = source_receipt_ref.map(str::to_owned);
+ self.transact_or(
+ |error| format!("failed to run delivery attempt transaction: {error}"),
+ async move |transaction| {
+ let project_id = project_id.as_str();
+ let attempt = &attempt;
+ ensure_fanout_identity(&transaction, project_id, attempt).await?;
- if let Some(stored) = read_delivery_attempt(&transaction, project_id, attempt).await? {
- if stored.attempt != *attempt {
- return Err("delivery attempt identity conflict".to_owned());
- }
- let outcome = if stored.outcome.is_some() {
- DeliveryAttemptClaimV1::AlreadySettled(stored.into_settlement()?)
- } else {
- DeliveryAttemptClaimV1::ReplayedAttempt
- };
- if let Some(source_receipt_ref) = source_receipt_ref {
- bind_source_receipt(&transaction, project_id, attempt, source_receipt_ref).await?;
- }
- transaction
- .commit()
- .await
- .map_err(|error| format!("failed to close delivery attempt replay: {error}"))?;
- return Ok(outcome);
- }
+ if let Some(stored) =
+ read_delivery_attempt(&transaction, project_id, attempt).await?
+ {
+ if stored.attempt != *attempt {
+ return Err("delivery attempt identity conflict".to_owned());
+ }
+ let outcome = if stored.outcome.is_some() {
+ DeliveryAttemptClaimV1::AlreadySettled(stored.into_settlement()?)
+ } else {
+ DeliveryAttemptClaimV1::ReplayedAttempt
+ };
+ if let Some(source_receipt_ref) = source_receipt_ref.as_deref() {
+ bind_source_receipt(&transaction, project_id, attempt, source_receipt_ref)
+ .await?;
+ }
+ return Ok(outcome);
+ }
- let existing = count_attempts(&transaction, project_id, attempt).await?;
- if existing >= u64::from(attempt.eligible) {
- return Err("delivery attempt exceeds eligible recipients".to_owned());
- }
- transaction
- .execute(
- "INSERT INTO delivery_settlements
- (project_id, owner_event_id, surface, channel_ref, attempted_at_micros,
- outcome, settled_at_micros, drop_reason, census_json)
- VALUES (?1, ?2, ?3, ?4, ?5, NULL, NULL, NULL, NULL)",
- tracedecay_runtime_core::db::engine::params![
- project_id,
- attempt.owner_event_id.as_str(),
- surface_name(attempt.channel.surface),
- attempt.channel.channel_ref.as_str(),
- attempt.attempted_at.0,
- ],
- )
- .await
- .map_err(|error| format!("failed to record delivery attempt: {error}"))?;
- if let Some(source_receipt_ref) = source_receipt_ref {
- bind_source_receipt(&transaction, project_id, attempt, source_receipt_ref).await?;
- }
- transaction
- .commit()
- .await
- .map_err(|error| format!("failed to commit delivery attempt: {error}"))?;
- Ok(DeliveryAttemptClaimV1::Claimed)
+ let existing = count_attempts(&transaction, project_id, attempt).await?;
+ if existing >= u64::from(attempt.eligible) {
+ return Err("delivery attempt exceeds eligible recipients".to_owned());
+ }
+ transaction
+ .execute(
+ "INSERT INTO delivery_settlements
+ (project_id, owner_event_id, surface, channel_ref, attempted_at_micros,
+ outcome, settled_at_micros, drop_reason, census_json)
+ VALUES (?1, ?2, ?3, ?4, ?5, NULL, NULL, NULL, NULL)",
+ tracedecay_runtime_core::db::engine::params![
+ project_id,
+ attempt.owner_event_id.as_str(),
+ surface_name(attempt.channel.surface),
+ attempt.channel.channel_ref.as_str(),
+ attempt.attempted_at.0,
+ ],
+ )
+ .await
+ .map_err(|error| format!("failed to record delivery attempt: {error}"))?;
+ if let Some(source_receipt_ref) = source_receipt_ref.as_deref() {
+ bind_source_receipt(&transaction, project_id, attempt, source_receipt_ref)
+ .await?;
+ }
+ Ok(DeliveryAttemptClaimV1::Claimed)
+ },
+ )
+ .await
}
/// Resolves a project-scoped source acknowledgement token to the exact
@@ -359,67 +363,68 @@ impl RegisteredGlobalDb {
settlement
.validate()
.map_err(|error| format!("invalid delivery settlement: {error}"))?;
- let transaction = self
- .begin_write_transaction()
- .await
- .map_err(|error| format!("failed to begin delivery settlement transaction: {error}"))?;
- ensure_fanout_identity(&transaction, project_id, &settlement.attempt).await?;
- let stored = read_delivery_attempt(&transaction, project_id, &settlement.attempt)
- .await?
- .ok_or_else(|| "delivery attempt is unavailable".to_owned())?;
- if stored.attempt != settlement.attempt {
- return Err("delivery attempt identity conflict".to_owned());
- }
- let (replayed, census) = if stored.outcome.is_some() {
- let census = stored
- .census
- .clone()
- .ok_or_else(|| "delivery settlement census is missing".to_owned())?;
- if stored.into_settlement()? != *settlement {
- return Err("delivery settlement conflict".to_owned());
- }
- (true, census)
- } else {
- let census = read_pending_delivery_census(
- &transaction,
- project_id,
- &settlement.attempt,
- settlement,
- )
- .await?;
- let census_json = serde_json::to_string(&census)
- .map_err(|error| format!("failed to serialize delivery census: {error}"))?;
- transaction
- .execute(
- "UPDATE delivery_settlements
- SET outcome = ?5, settled_at_micros = ?6,
- drop_reason = ?7, census_json = ?8
- WHERE project_id = ?1 AND owner_event_id = ?2 AND surface = ?3
- AND channel_ref = ?4 AND outcome IS NULL",
- tracedecay_runtime_core::db::engine::params![
+ let project_id = project_id.to_owned();
+ let settlement = settlement.clone();
+ self.transact_or(
+ |error| format!("failed to run delivery settlement transaction: {error}"),
+ async move |transaction| {
+ let project_id = project_id.as_str();
+ ensure_fanout_identity(&transaction, project_id, &settlement.attempt).await?;
+ let stored = read_delivery_attempt(&transaction, project_id, &settlement.attempt)
+ .await?
+ .ok_or_else(|| "delivery attempt is unavailable".to_owned())?;
+ if stored.attempt != settlement.attempt {
+ return Err("delivery attempt identity conflict".to_owned());
+ }
+ let (replayed, census) = if stored.outcome.is_some() {
+ let census = stored
+ .census
+ .clone()
+ .ok_or_else(|| "delivery settlement census is missing".to_owned())?;
+ if stored.into_settlement()? != settlement {
+ return Err("delivery settlement conflict".to_owned());
+ }
+ (true, census)
+ } else {
+ let census = read_pending_delivery_census(
+ &transaction,
project_id,
- settlement.attempt.owner_event_id.as_str(),
- surface_name(settlement.attempt.channel.surface),
- settlement.attempt.channel.channel_ref.as_str(),
- outcome_name(settlement.outcome),
- settlement.settled_at.0,
- settlement.drop_reason.map(drop_reason_name),
- census_json,
- ],
- )
- .await
- .map_err(|error| format!("failed to settle delivery attempt: {error}"))?;
- (false, census)
- };
- transaction
- .commit()
- .await
- .map_err(|error| format!("failed to commit delivery settlement: {error}"))?;
- Ok(DurableDeliverySettlementReceiptV1 {
- settlement: settlement.clone(),
- census,
- replayed,
- })
+ &settlement.attempt,
+ &settlement,
+ )
+ .await?;
+ let census_json = serde_json::to_string(&census)
+ .map_err(|error| format!("failed to serialize delivery census: {error}"))?;
+ transaction
+ .execute(
+ "UPDATE delivery_settlements
+ SET outcome = ?5, settled_at_micros = ?6,
+ drop_reason = ?7, census_json = ?8
+ WHERE project_id = ?1 AND owner_event_id = ?2 AND surface = ?3
+ AND channel_ref = ?4 AND outcome IS NULL",
+ tracedecay_runtime_core::db::engine::params![
+ project_id,
+ settlement.attempt.owner_event_id.as_str(),
+ surface_name(settlement.attempt.channel.surface),
+ settlement.attempt.channel.channel_ref.as_str(),
+ outcome_name(settlement.outcome),
+ settlement.settled_at.0,
+ settlement.drop_reason.map(drop_reason_name),
+ census_json,
+ ],
+ )
+ .await
+ .map_err(|error| format!("failed to settle delivery attempt: {error}"))?;
+ (false, census)
+ };
+ Ok(DurableDeliverySettlementReceiptV1 {
+ settlement: settlement.clone(),
+ census,
+ replayed,
+ })
+ },
+ )
+ .await
}
}
@@ -497,7 +502,7 @@ fn validate_source_receipt_ref(source_receipt_ref: &str) -> Result<(), String> {
}
async fn bind_source_receipt(
- transaction: &RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl Executor,
project_id: &str,
attempt: &DeliverySettlementAttemptV1,
source_receipt_ref: &str,
@@ -587,7 +592,7 @@ async fn bind_source_receipt(
}
async fn ensure_fanout_identity(
- transaction: &RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl Executor,
project_id: &str,
attempt: &DeliverySettlementAttemptV1,
) -> Result<(), String> {
@@ -697,7 +702,7 @@ impl StoredDeliveryAttempt {
}
async fn read_delivery_attempt(
- transaction: &RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl QueryExecutor,
project_id: &str,
attempt: &DeliverySettlementAttemptV1,
) -> Result, String> {
@@ -756,7 +761,7 @@ async fn read_delivery_attempt(
}
async fn count_attempts(
- transaction: &RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl QueryExecutor,
project_id: &str,
attempt: &DeliverySettlementAttemptV1,
) -> Result {
@@ -785,7 +790,7 @@ async fn count_attempts(
}
async fn read_pending_delivery_census(
- transaction: &RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl QueryExecutor,
project_id: &str,
attempt: &DeliverySettlementAttemptV1,
settlement: &DeliverySettlementV1,
diff --git a/crates/tracedecay-global-db/src/stack_delivery.rs b/crates/tracedecay-global-db/src/stack_delivery.rs
index 585fc67f37..0e2ff3e286 100644
--- a/crates/tracedecay-global-db/src/stack_delivery.rs
+++ b/crates/tracedecay-global-db/src/stack_delivery.rs
@@ -10,7 +10,7 @@ use std::collections::{BTreeMap, BTreeSet};
use serde::{Deserialize, Serialize};
use tracedecay_runtime_core::db::engine::{Executor, QueryExecutor, params, params_from_iter};
-use crate::RegisteredGlobalDb;
+use crate::{RegisteredGlobalDb, registered::JobExit};
/// Maximum number of rows in the `pending` state for one project.
pub const MAX_GITHUB_STACK_ACTIVE_PENDING_V1: usize = 256;
@@ -540,93 +540,88 @@ impl RegisteredGlobalDb {
MAX_GITHUB_STACK_DELIVERY_BATCH_V1
));
}
- let transaction = self
- .begin_write_transaction()
- .await
- .map_err(|error| format!("begin GitHub stack signal append: {error}"))?;
- if let Some(existing) =
- lookup_signal(&transaction, &record.project_id, &record.signal_id).await?
- {
- if existing != record {
+ self.transact_or_rollback(
+ |error| format!("GitHub stack signal append: {error}"),
+ async move |transaction| {
+ if let Some(existing) =
+ lookup_signal(&transaction, &record.project_id, &record.signal_id).await?
+ {
+ if existing != record {
+ return Err("GitHub stack signal identity conflict".to_owned().into());
+ }
+ let (pending_count, deferred_count) =
+ counts(&transaction, &record.project_id).await?;
+ return Err(JobExit::Rollback(
+ GitHubStackSignalAppendOutcomeV1::Replayed {
+ pending_count,
+ deferred_count,
+ },
+ ));
+ }
+
transaction
- .rollback()
+ .execute(
+ "INSERT INTO github_stack_delivery_signals
+ (project_id, signal_id, scope_digest, repository_id,
+ watermark_id, observed_at_micros, signal_json)
+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
+ params![
+ record.project_id.as_str(),
+ record.signal_id.as_str(),
+ record.scope_digest.as_str(),
+ record.repository_id.as_str(),
+ record.watermark_id.as_str(),
+ record.observed_at_micros,
+ record.signal_json.as_str()
+ ],
+ )
.await
- .map_err(|error| format!("close conflicting GitHub stack append: {error}"))?;
- return Err("GitHub stack signal identity conflict".to_owned());
- }
- let (pending_count, deferred_count) = counts(&transaction, &record.project_id).await?;
- transaction
- .rollback()
- .await
- .map_err(|error| format!("close replayed GitHub stack append: {error}"))?;
- return Ok(GitHubStackSignalAppendOutcomeV1::Replayed {
- pending_count,
- deferred_count,
- });
- }
-
- transaction
- .execute(
- "INSERT INTO github_stack_delivery_signals
- (project_id, signal_id, scope_digest, repository_id,
- watermark_id, observed_at_micros, signal_json)
- VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
- params![
- record.project_id.as_str(),
- record.signal_id.as_str(),
- record.scope_digest.as_str(),
- record.repository_id.as_str(),
- record.watermark_id.as_str(),
- record.observed_at_micros,
- record.signal_json.as_str()
- ],
- )
- .await
- .map_err(|error| format!("append GitHub stack signal: {error}"))?;
- // Repair a deferred queue left by an interrupted older writer before
- // assigning this append's capacity. All callers hold the immediate
- // write transaction, so the quota decision is serialized.
- promote_deferred(&transaction, &record.project_id).await?;
- let (pending_before, _) = counts(&transaction, &record.project_id).await?;
- let capacity = MAX_GITHUB_STACK_ACTIVE_PENDING_V1.saturating_sub(pending_before);
- for (index, recipient) in recipient_set.into_iter().enumerate() {
- let state = if index < capacity {
- GitHubStackDeliveryStateV1::Pending
- } else {
- GitHubStackDeliveryStateV1::Deferred
- };
- transaction
- .execute(
- "INSERT INTO github_stack_delivery_recipients
- (project_id, signal_id, recipient, state)
- VALUES (?1, ?2, ?3, ?4)",
- params![
- record.project_id.as_str(),
- record.signal_id.as_str(),
- recipient,
- state.as_str()
- ],
- )
- .await
- .map_err(|error| format!("append GitHub stack delivery recipient: {error}"))?;
- }
- let (pending_count, deferred_count) = counts(&transaction, &record.project_id).await?;
- let saturated = deferred_count > 0;
- transaction
- .commit()
- .await
- .map_err(|error| format!("commit GitHub stack signal append: {error}"))?;
- Ok(if saturated {
- GitHubStackSignalAppendOutcomeV1::Saturated {
- pending_count,
- deferred_count,
- }
- } else {
- GitHubStackSignalAppendOutcomeV1::Appended {
- pending_count,
- deferred_count,
- }
- })
+ .map_err(|error| format!("append GitHub stack signal: {error}"))?;
+ // Repair a deferred queue left by an interrupted older writer before
+ // assigning this append's capacity. The write job is serialized, so
+ // the quota decision is too.
+ promote_deferred(&transaction, &record.project_id).await?;
+ let (pending_before, _) = counts(&transaction, &record.project_id).await?;
+ let capacity = MAX_GITHUB_STACK_ACTIVE_PENDING_V1.saturating_sub(pending_before);
+ for (index, recipient) in recipient_set.iter().enumerate() {
+ let state = if index < capacity {
+ GitHubStackDeliveryStateV1::Pending
+ } else {
+ GitHubStackDeliveryStateV1::Deferred
+ };
+ transaction
+ .execute(
+ "INSERT INTO github_stack_delivery_recipients
+ (project_id, signal_id, recipient, state)
+ VALUES (?1, ?2, ?3, ?4)",
+ params![
+ record.project_id.as_str(),
+ record.signal_id.as_str(),
+ recipient.as_str(),
+ state.as_str()
+ ],
+ )
+ .await
+ .map_err(|error| {
+ format!("append GitHub stack delivery recipient: {error}")
+ })?;
+ }
+ let (pending_count, deferred_count) =
+ counts(&transaction, &record.project_id).await?;
+ Ok(if deferred_count > 0 {
+ GitHubStackSignalAppendOutcomeV1::Saturated {
+ pending_count,
+ deferred_count,
+ }
+ } else {
+ GitHubStackSignalAppendOutcomeV1::Appended {
+ pending_count,
+ deferred_count,
+ }
+ })
+ },
+ )
+ .await
}
/// Returns a deterministic page of coordinator-pending deliveries.
@@ -742,46 +737,47 @@ impl RegisteredGlobalDb {
validate_text(&key.signal_id, "signal id")?;
validate_recipient(&key.recipient)?;
}
- let transaction = self
- .begin_write_transaction()
- .await
- .map_err(|error| format!("begin GitHub stack host publication: {error}"))?;
- let batch = read_delivery_batch(&transaction, project_id, &keys).await?;
- let mut pending = Vec::with_capacity(keys.len());
- for key in &keys {
- let row = batch
- .get(key)
- .ok_or_else(|| "GitHub stack delivery batch is incomplete".to_owned())?;
- let signal_watermark = row
- .watermark_id
- .as_deref()
- .ok_or_else(|| "GitHub stack delivery signal is unavailable".to_owned())?;
- if signal_watermark != watermark_id {
- return Err("GitHub stack delivery watermark mismatch".to_owned());
- }
- let state = row
- .state
- .ok_or_else(|| "GitHub stack recipient binding is unavailable".to_owned())?;
- match state {
- GitHubStackDeliveryStateV1::Pending => {
- pending.push(key.clone());
+ let project_id = project_id.to_owned();
+ let watermark_id = watermark_id.to_owned();
+ self.transact_or(
+ |error| format!("GitHub stack host publication: {error}"),
+ async move |transaction| {
+ let batch = read_delivery_batch(&transaction, &project_id, &keys).await?;
+ let mut pending = Vec::with_capacity(keys.len());
+ for key in &keys {
+ let row = batch
+ .get(key)
+ .ok_or_else(|| "GitHub stack delivery batch is incomplete".to_owned())?;
+ let signal_watermark = row
+ .watermark_id
+ .as_deref()
+ .ok_or_else(|| "GitHub stack delivery signal is unavailable".to_owned())?;
+ if signal_watermark != watermark_id {
+ return Err("GitHub stack delivery watermark mismatch".to_owned());
+ }
+ let state = row.state.ok_or_else(|| {
+ "GitHub stack recipient binding is unavailable".to_owned()
+ })?;
+ match state {
+ GitHubStackDeliveryStateV1::Pending => {
+ pending.push(key.clone());
+ }
+ GitHubStackDeliveryStateV1::HostPending => {}
+ GitHubStackDeliveryStateV1::Deferred => {
+ return Err("GitHub stack delivery is deferred".to_owned());
+ }
+ GitHubStackDeliveryStateV1::Settled => {}
+ GitHubStackDeliveryStateV1::AuthorizationLost => {
+ return Err("GitHub stack delivery authorization was lost".to_owned());
+ }
+ }
}
- GitHubStackDeliveryStateV1::HostPending => {}
- GitHubStackDeliveryStateV1::Deferred => {
- return Err("GitHub stack delivery is deferred".to_owned());
- }
- GitHubStackDeliveryStateV1::Settled => {}
- GitHubStackDeliveryStateV1::AuthorizationLost => {
- return Err("GitHub stack delivery authorization was lost".to_owned());
- }
- }
- }
- transition_pending_batch(&transaction, project_id, &pending).await?;
- promote_deferred(&transaction, project_id).await?;
- transaction
- .commit()
- .await
- .map_err(|error| format!("commit GitHub stack host publication: {error}"))
+ transition_pending_batch(&transaction, &project_id, &pending).await?;
+ promote_deferred(&transaction, &project_id).await?;
+ Ok(())
+ },
+ )
+ .await
}
/// Final host receipt. Replaying a receipt after settlement is harmless;
@@ -800,58 +796,70 @@ impl RegisteredGlobalDb {
validate_text(project_id, "project id")?;
validate_text(signal_id, "signal id")?;
validate_recipient(recipient)?;
- let transaction = self
- .begin_write_transaction()
- .await
- .map_err(|error| format!("begin GitHub stack host acknowledgement: {error}"))?;
- let mut rows = transaction
- .query(
- "SELECT state FROM github_stack_delivery_recipients
- WHERE project_id = ?1 AND signal_id = ?2 AND recipient = ?3",
- params![project_id, signal_id, recipient],
- )
- .await
- .map_err(|error| format!("query GitHub stack host acknowledgement: {error}"))?;
- let Some(row) = rows
- .next()
- .await
- .map_err(|error| format!("read GitHub stack host acknowledgement: {error}"))?
- else {
- return Err("GitHub stack recipient binding is unavailable".to_owned());
- };
- let state = GitHubStackDeliveryStateV1::parse(
- row.get::(0)
- .map_err(|error| format!("decode GitHub stack host state: {error}"))?,
- )?;
- match state {
- GitHubStackDeliveryStateV1::HostPending => {
- transaction
- .execute(
- "UPDATE github_stack_delivery_recipients
- SET state = 'settled'
- WHERE project_id = ?1 AND signal_id = ?2 AND recipient = ?3
- AND state = 'host_pending'",
- params![project_id, signal_id, recipient],
+ let (project_id, signal_id, recipient) = (
+ project_id.to_owned(),
+ signal_id.to_owned(),
+ recipient.to_owned(),
+ );
+ self.transact_or(
+ |error| format!("GitHub stack host acknowledgement: {error}"),
+ async move |transaction| {
+ let mut rows = transaction
+ .query(
+ "SELECT state FROM github_stack_delivery_recipients
+ WHERE project_id = ?1 AND signal_id = ?2 AND recipient = ?3",
+ params![project_id.as_str(), signal_id.as_str(), recipient.as_str()],
)
.await
- .map_err(|error| format!("settle GitHub stack host delivery: {error}"))?;
- }
- GitHubStackDeliveryStateV1::Settled => {}
- GitHubStackDeliveryStateV1::Pending => {
- return Err("GitHub stack delivery is still pending host publication".to_owned());
- }
- GitHubStackDeliveryStateV1::Deferred => {
- return Err("GitHub stack delivery is deferred".to_owned());
- }
- GitHubStackDeliveryStateV1::AuthorizationLost => {
- return Err("GitHub stack delivery authorization was lost".to_owned());
- }
- }
- promote_deferred(&transaction, project_id).await?;
- transaction
- .commit()
- .await
- .map_err(|error| format!("commit GitHub stack host acknowledgement: {error}"))
+ .map_err(|error| format!("query GitHub stack host acknowledgement: {error}"))?;
+ let Some(row) = rows
+ .next()
+ .await
+ .map_err(|error| format!("read GitHub stack host acknowledgement: {error}"))?
+ else {
+ return Err("GitHub stack recipient binding is unavailable".to_owned());
+ };
+ let state = GitHubStackDeliveryStateV1::parse(
+ row.get::(0)
+ .map_err(|error| format!("decode GitHub stack host state: {error}"))?,
+ )?;
+ match state {
+ GitHubStackDeliveryStateV1::HostPending => {
+ transaction
+ .execute(
+ "UPDATE github_stack_delivery_recipients
+ SET state = 'settled'
+ WHERE project_id = ?1 AND signal_id = ?2 AND recipient = ?3
+ AND state = 'host_pending'",
+ params![
+ project_id.as_str(),
+ signal_id.as_str(),
+ recipient.as_str()
+ ],
+ )
+ .await
+ .map_err(|error| {
+ format!("settle GitHub stack host delivery: {error}")
+ })?;
+ }
+ GitHubStackDeliveryStateV1::Settled => {}
+ GitHubStackDeliveryStateV1::Pending => {
+ return Err(
+ "GitHub stack delivery is still pending host publication".to_owned()
+ );
+ }
+ GitHubStackDeliveryStateV1::Deferred => {
+ return Err("GitHub stack delivery is deferred".to_owned());
+ }
+ GitHubStackDeliveryStateV1::AuthorizationLost => {
+ return Err("GitHub stack delivery authorization was lost".to_owned());
+ }
+ }
+ promote_deferred(&transaction, &project_id).await?;
+ Ok(())
+ },
+ )
+ .await
}
/// Marks a pending/deferred recipient as permanently unauthorized. The
@@ -866,49 +874,53 @@ impl RegisteredGlobalDb {
validate_text(project_id, "project id")?;
validate_text(signal_id, "signal id")?;
validate_recipient(recipient)?;
- let transaction = self
- .begin_write_transaction()
- .await
- .map_err(|error| format!("begin GitHub stack authorization loss: {error}"))?;
- let changed = transaction
- .execute(
- "UPDATE github_stack_delivery_recipients
- SET state = 'authorization_lost'
- WHERE project_id = ?1 AND signal_id = ?2 AND recipient = ?3
- AND state IN ('pending', 'deferred')",
- params![project_id, signal_id, recipient],
- )
- .await
- .map_err(|error| format!("record GitHub stack authorization loss: {error}"))?;
- if changed == 0 {
- let mut rows = transaction
- .query(
- "SELECT state FROM github_stack_delivery_recipients
- WHERE project_id = ?1 AND signal_id = ?2 AND recipient = ?3",
- params![project_id, signal_id, recipient],
- )
- .await
- .map_err(|error| format!("query GitHub stack authorization state: {error}"))?;
- let Some(row) = rows
- .next()
- .await
- .map_err(|error| format!("read GitHub stack authorization state: {error}"))?
- else {
- return Err("GitHub stack recipient binding is unavailable".to_owned());
- };
- let state =
- GitHubStackDeliveryStateV1::parse(row.get::(0).map_err(|error| {
- format!("decode GitHub stack authorization state: {error}")
- })?)?;
- if state != GitHubStackDeliveryStateV1::AuthorizationLost {
- return Err("GitHub stack delivery is no longer authorizable".to_owned());
- }
- }
- promote_deferred(&transaction, project_id).await?;
- transaction
- .commit()
- .await
- .map_err(|error| format!("commit GitHub stack authorization loss: {error}"))
+ let (project_id, signal_id, recipient) = (
+ project_id.to_owned(),
+ signal_id.to_owned(),
+ recipient.to_owned(),
+ );
+ self.transact_or(
+ |error| format!("GitHub stack authorization loss: {error}"),
+ async move |transaction| {
+ let changed = transaction
+ .execute(
+ "UPDATE github_stack_delivery_recipients
+ SET state = 'authorization_lost'
+ WHERE project_id = ?1 AND signal_id = ?2 AND recipient = ?3
+ AND state IN ('pending', 'deferred')",
+ params![project_id.as_str(), signal_id.as_str(), recipient.as_str()],
+ )
+ .await
+ .map_err(|error| format!("record GitHub stack authorization loss: {error}"))?;
+ if changed == 0 {
+ let mut rows = transaction
+ .query(
+ "SELECT state FROM github_stack_delivery_recipients
+ WHERE project_id = ?1 AND signal_id = ?2 AND recipient = ?3",
+ params![project_id.as_str(), signal_id.as_str(), recipient.as_str()],
+ )
+ .await
+ .map_err(|error| {
+ format!("query GitHub stack authorization state: {error}")
+ })?;
+ let Some(row) = rows.next().await.map_err(|error| {
+ format!("read GitHub stack authorization state: {error}")
+ })?
+ else {
+ return Err("GitHub stack recipient binding is unavailable".to_owned());
+ };
+ let state = GitHubStackDeliveryStateV1::parse(row.get::(0).map_err(
+ |error| format!("decode GitHub stack authorization state: {error}"),
+ )?)?;
+ if state != GitHubStackDeliveryStateV1::AuthorizationLost {
+ return Err("GitHub stack delivery is no longer authorizable".to_owned());
+ }
+ }
+ promote_deferred(&transaction, &project_id).await?;
+ Ok(())
+ },
+ )
+ .await
}
/// Looks up the immutable signal payload by its exact registered-project
From 45baa936c45fa216684c440099ee063e27c3fbe9 Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 16:14:15 +0000
Subject: [PATCH 11/89] refactor(global-db): run source rewrite writes as jobs
---
.../src/registered_source_rewrite.rs | 297 +++++++++---------
1 file changed, 152 insertions(+), 145 deletions(-)
diff --git a/crates/tracedecay-global-db/src/registered_source_rewrite.rs b/crates/tracedecay-global-db/src/registered_source_rewrite.rs
index 48cc673b79..e36a2eab5b 100644
--- a/crates/tracedecay-global-db/src/registered_source_rewrite.rs
+++ b/crates/tracedecay-global-db/src/registered_source_rewrite.rs
@@ -12,11 +12,12 @@ use tracedecay_domain::{
CanonicalObservationIdV1, ObservationScopeV1, ObservationSourceGenerationV1,
ObservationSourceIdentityV1,
};
-use tracedecay_runtime_core::db::engine::{QueryExecutor, params};
+use tracedecay_runtime_core::db::engine::{Executor, QueryExecutor, params};
use tracedecay_store::{ProjectionStoreError, ProjectionStoreResult};
use super::RegisteredGlobalDb;
use super::observation_projection::retire_source_record;
+use super::registered::JobExit;
/// Unoffered-record reads are keyset-paginated at this page size. The exact-
/// SQL engine refuses a query that materializes more rows than its hard cap,
@@ -87,33 +88,42 @@ impl RegisteredGlobalDb {
return Ok(());
}
let key = source_key(source, scope)?;
- let transaction = self
- .begin_write_transaction()
- .await
- .map_err(|error| storage("begin source presence transaction", error))?;
- for record in records {
- transaction
- .execute(
- "INSERT INTO observation_source_presence (
- source_key, observation_id, generation, start_offset
- ) VALUES (?1, ?2, ?3, ?4)
- ON CONFLICT(source_key, observation_id) DO UPDATE SET
- generation = excluded.generation,
- start_offset = excluded.start_offset",
- params![
- key.as_str(),
- record.observation_id.as_str(),
- record.generation.generation_id().to_string(),
- offset(record.start_offset)?,
- ],
- )
- .await
- .map_err(|error| storage("record source presence", error))?;
- }
- transaction
- .commit()
- .await
- .map_err(|error| storage("commit source presence", error))
+ let rows = records
+ .iter()
+ .map(|record| {
+ Ok((
+ record.observation_id.as_str().to_owned(),
+ record.generation.generation_id().to_string(),
+ offset(record.start_offset)?,
+ ))
+ })
+ .collect::>>()?;
+ self.transact_or(
+ |error| storage("run source presence transaction", error),
+ async move |transaction| {
+ for (observation_id, generation, start_offset) in &rows {
+ transaction
+ .execute(
+ "INSERT INTO observation_source_presence (
+ source_key, observation_id, generation, start_offset
+ ) VALUES (?1, ?2, ?3, ?4)
+ ON CONFLICT(source_key, observation_id) DO UPDATE SET
+ generation = excluded.generation,
+ start_offset = excluded.start_offset",
+ params![
+ key.as_str(),
+ observation_id.as_str(),
+ generation.as_str(),
+ *start_offset,
+ ],
+ )
+ .await
+ .map_err(|error| storage("record source presence", error))?;
+ }
+ Ok(())
+ },
+ )
+ .await
}
/// Starts a rewrite of the source from `previous` to `generation`. The
@@ -138,44 +148,45 @@ impl RegisteredGlobalDb {
}
let key = source_key(source, scope)?;
let generation = generation.generation_id().to_string();
- let transaction = self
- .begin_write_transaction()
- .await
- .map_err(|error| storage("begin source rewrite transaction", error))?;
- // A rewrite the previous generation never finished still names its
- // own layout: its retained prefix was relabeled to it.
- let unfinished = pending_rewrite(&transaction, &key).await?;
- transaction
- .execute(
- "UPDATE observation_source_presence SET generation = ?2
- WHERE source_key = ?1 AND start_offset < ?3
- AND generation IN (?4, ?5)",
- params![
- key.as_str(),
- generation.as_str(),
- offset(retained_through)?,
- previous.generation_id().to_string(),
- unfinished,
- ],
- )
- .await
- .map_err(|error| storage("retain source prefix", error))?;
- transaction
- .execute(
- "INSERT INTO observation_source_rewrites (
- source_key, generation, retained_through
- ) VALUES (?1, ?2, ?3)
- ON CONFLICT(source_key) DO UPDATE SET
- generation = excluded.generation,
- retained_through = excluded.retained_through",
- params![key.as_str(), generation.as_str(), offset(retained_through)?],
- )
- .await
- .map_err(|error| storage("record source rewrite", error))?;
- transaction
- .commit()
- .await
- .map_err(|error| storage("commit source rewrite", error))
+ let retained_through = offset(retained_through)?;
+ let previous = previous.generation_id().to_string();
+ self.transact_or(
+ |error| storage("run source rewrite transaction", error),
+ async move |transaction| {
+ // A rewrite the previous generation never finished still names its
+ // own layout: its retained prefix was relabeled to it.
+ let unfinished = pending_rewrite(&transaction, &key).await?;
+ transaction
+ .execute(
+ "UPDATE observation_source_presence SET generation = ?2
+ WHERE source_key = ?1 AND start_offset < ?3
+ AND generation IN (?4, ?5)",
+ params![
+ key.as_str(),
+ generation.as_str(),
+ retained_through,
+ previous.as_str(),
+ unfinished.as_deref(),
+ ],
+ )
+ .await
+ .map_err(|error| storage("retain source prefix", error))?;
+ transaction
+ .execute(
+ "INSERT INTO observation_source_rewrites (
+ source_key, generation, retained_through
+ ) VALUES (?1, ?2, ?3)
+ ON CONFLICT(source_key) DO UPDATE SET
+ generation = excluded.generation,
+ retained_through = excluded.retained_through",
+ params![key.as_str(), generation.as_str(), retained_through],
+ )
+ .await
+ .map_err(|error| storage("record source rewrite", error))?;
+ Ok(())
+ },
+ )
+ .await
}
/// Completes a pending rewrite once `generation` was read to the end of
@@ -202,85 +213,81 @@ impl RegisteredGlobalDb {
return Ok(0);
}
drop(snapshot);
- let transaction = self
- .begin_write_transaction()
- .await
- .map_err(|error| storage("begin source rewrite completion", error))?;
- if pending_rewrite(&transaction, &key).await?.as_deref() != Some(generation.as_str()) {
- transaction
- .rollback()
- .await
- .map_err(|error| storage("release source rewrite completion", error))?;
- return Ok(0);
- }
- let mut retired = 0_u64;
- // Keyset-paginate the unoffered ids instead of materializing the whole
- // set in one statement: a single rewritten source can hold more
- // unoffered records than the engine's per-query materialization cap,
- // and one oversized read wedged every completion attempt forever.
- // `retire_source_record` never touches `observation_source_presence`,
- // so the ordered id cursor stays stable across pages; the settle
- // deletes below remain the authoritative cleanup.
- let mut after_observation_id = String::new();
- loop {
- let mut rows = transaction
- .query(
- "SELECT observation_id FROM observation_source_presence
- WHERE source_key = ?1 AND generation <> ?2
- AND observation_id > ?3
- ORDER BY observation_id
- LIMIT ?4",
- params![
- key.as_str(),
- generation.as_str(),
- after_observation_id,
- UNOFFERED_RECORD_PAGE_SIZE
- ],
- )
- .await
- .map_err(|error| storage("read unoffered source records", error))?;
- let mut page = Vec::new();
- while let Some(row) = rows
- .next()
- .await
- .map_err(|error| storage("read unoffered source records", error))?
- {
- page.push(
- row.get::(0)
- .map_err(|error| storage("read unoffered source records", error))?,
- );
- }
- drop(rows);
- if page.is_empty() {
- break;
- }
- let page_len = page.len();
- after_observation_id = page[page_len - 1].clone();
- for observation_id in &page {
- let retired_one: Pin<
- Box> + Send + '_>,
- > = Box::pin(retire_source_record(&transaction, observation_id));
- if retired_one.await? {
- retired += 1;
+ self.transact_or_rollback(
+ |error| storage("run source rewrite completion", error),
+ async move |transaction| {
+ if pending_rewrite(&transaction, &key).await?.as_deref()
+ != Some(generation.as_str())
+ {
+ return Err(JobExit::Rollback(0));
}
- }
- if (page_len as i64) < UNOFFERED_RECORD_PAGE_SIZE {
- break;
- }
- }
- for sql in [
- "DELETE FROM observation_source_presence WHERE source_key = ?1 AND generation <> ?2",
- "DELETE FROM observation_source_rewrites WHERE source_key = ?1 AND generation = ?2",
- ] {
- transaction
- .execute(sql, params![key.as_str(), generation.as_str()])
- .await
- .map_err(|error| storage("settle source rewrite", error))?;
- }
- transaction
- .commit()
- .await
- .map_err(|error| storage("commit source rewrite completion", error))?;
- Ok(retired)
+ let mut retired = 0_u64;
+ // Keyset-paginate the unoffered ids instead of materializing the whole
+ // set in one statement: a single rewritten source can hold more
+ // unoffered records than the engine's per-query materialization cap,
+ // and one oversized read wedged every completion attempt forever.
+ // `retire_source_record` never touches `observation_source_presence`,
+ // so the ordered id cursor stays stable across pages; the settle
+ // deletes below remain the authoritative cleanup.
+ let mut after_observation_id = String::new();
+ loop {
+ let mut rows = transaction
+ .query(
+ "SELECT observation_id FROM observation_source_presence
+ WHERE source_key = ?1 AND generation <> ?2
+ AND observation_id > ?3
+ ORDER BY observation_id
+ LIMIT ?4",
+ params![
+ key.as_str(),
+ generation.as_str(),
+ after_observation_id.as_str(),
+ UNOFFERED_RECORD_PAGE_SIZE
+ ],
+ )
+ .await
+ .map_err(|error| storage("read unoffered source records", error))?;
+ let mut page = Vec::new();
+ while let Some(row) = rows
+ .next()
+ .await
+ .map_err(|error| storage("read unoffered source records", error))?
+ {
+ page.push(
+ row.get::(0)
+ .map_err(|error| storage("read unoffered source records", error))?,
+ );
+ }
+ drop(rows);
+ if page.is_empty() {
+ break;
+ }
+ let page_len = page.len();
+ after_observation_id = page[page_len - 1].clone();
+ for observation_id in &page {
+ let retired_one: Pin<
+ Box> + Send + '_>,
+ > = Box::pin(retire_source_record(&transaction, observation_id));
+ if retired_one.await? {
+ retired += 1;
+ }
+ }
+ if (page_len as i64) < UNOFFERED_RECORD_PAGE_SIZE {
+ break;
+ }
+ }
+ for sql in [
+ "DELETE FROM observation_source_presence WHERE source_key = ?1 AND generation <> ?2",
+ "DELETE FROM observation_source_rewrites WHERE source_key = ?1 AND generation = ?2",
+ ] {
+ transaction
+ .execute(sql, params![key.as_str(), generation.as_str()])
+ .await
+ .map_err(|error| storage("settle source rewrite", error))?;
+ }
+ Ok(retired)
+ },
+ )
+ .await
}
}
From 6d7ab42a99e981c5389589d9ad16b0e9fcc2d5db Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 16:14:16 +0000
Subject: [PATCH 12/89] refactor(global-db): run registry and remote deletion
writes as jobs
---
.../src/registry_maintenance/lifecycle.rs | 41 ++--
.../src/remote_deletion.rs | 221 +++++++++---------
2 files changed, 131 insertions(+), 131 deletions(-)
diff --git a/crates/tracedecay-global-db/src/registry_maintenance/lifecycle.rs b/crates/tracedecay-global-db/src/registry_maintenance/lifecycle.rs
index 09ca393e33..2c7ee003c4 100644
--- a/crates/tracedecay-global-db/src/registry_maintenance/lifecycle.rs
+++ b/crates/tracedecay-global-db/src/registry_maintenance/lifecycle.rs
@@ -209,7 +209,7 @@ pub async fn apply_registry_gc(
}
pub(super) async fn delete_registry_gc_candidates_in_transaction(
- transaction: &RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl tracedecay_runtime_core::db::engine::Executor,
project_ids: &[String],
project_paths: &[PathBuf],
) -> tracedecay_domain::errors::Result<(usize, usize)> {
@@ -269,19 +269,22 @@ pub async fn forget_registry_project(
project_id: &str,
project_paths: &[PathBuf],
) -> tracedecay_domain::errors::Result {
- let transaction = db.begin_write_transaction().await?;
- let (code_projects_deleted, path_ledger_rows_deleted) =
- delete_registry_gc_candidates_in_transaction(
- &transaction,
- &[project_id.to_string()],
- project_paths,
- )
- .await?;
- transaction.commit().await?;
- Ok(ForgetRegistryProjectRows {
- code_projects_deleted,
- path_ledger_rows_deleted,
+ let project_ids = [project_id.to_string()];
+ let project_paths = project_paths.to_vec();
+ db.transact(async move |transaction| {
+ let (code_projects_deleted, path_ledger_rows_deleted) =
+ delete_registry_gc_candidates_in_transaction(
+ &transaction,
+ &project_ids,
+ &project_paths,
+ )
+ .await?;
+ Ok::<_, tracedecay_domain::errors::TraceDecayError>(ForgetRegistryProjectRows {
+ code_projects_deleted,
+ path_ledger_rows_deleted,
+ })
})
+ .await?
}
/// Retires exact registry rows for project roots already removed by an
@@ -290,9 +293,11 @@ pub async fn retire_registry_project_paths(
db: &RegisteredGlobalDb,
project_paths: &[PathBuf],
) -> tracedecay_domain::errors::Result {
- let transaction = db.begin_write_transaction().await?;
- let (_, retired) =
- delete_registry_gc_candidates_in_transaction(&transaction, &[], project_paths).await?;
- transaction.commit().await?;
- Ok(retired)
+ let project_paths = project_paths.to_vec();
+ db.transact(async move |transaction| {
+ let (_, retired) =
+ delete_registry_gc_candidates_in_transaction(&transaction, &[], &project_paths).await?;
+ Ok::<_, tracedecay_domain::errors::TraceDecayError>(retired)
+ })
+ .await?
}
diff --git a/crates/tracedecay-global-db/src/remote_deletion.rs b/crates/tracedecay-global-db/src/remote_deletion.rs
index d85975e412..8a4fd115c4 100644
--- a/crates/tracedecay-global-db/src/remote_deletion.rs
+++ b/crates/tracedecay-global-db/src/remote_deletion.rs
@@ -2,7 +2,7 @@
use serde::{Deserialize, Serialize};
use tracedecay_domain::errors::TraceDecayError;
-use tracedecay_runtime_core::db::engine::{QueryExecutor, params};
+use tracedecay_runtime_core::db::engine::{Executor, QueryExecutor, params};
use crate::RegisteredGlobalDb;
@@ -323,45 +323,44 @@ impl RegisteredGlobalDb {
tombstone: RemoteDeletionTombstone,
) -> Result {
tombstone.validate_new()?;
- let transaction = self.begin_write_transaction().await?;
- if let Some(existing) = read_tombstone(
- &transaction,
- &tombstone.profile_id,
- tombstone.target,
- tombstone.project_key(),
- )
- .await?
- {
- transaction.commit().await.map_err(|error| {
- remote_deletion_error("commit remote deletion tombstone replay", error)
- })?;
- return if existing.tombstone_id == tombstone.tombstone_id {
- Ok(RemoteDeletionTombstoneRecordOutcome::Replayed(existing))
- } else {
- Ok(RemoteDeletionTombstoneRecordOutcome::Conflict { existing })
- };
- }
- transaction
- .execute(
- "INSERT INTO remote_deletion_tombstones
- (profile_id, target_kind, project_id, tombstone_id, recorded_at_micros,
- cleanup_status, failure_code, failure_phase, retryable)
- VALUES (?1, ?2, ?3, ?4, ?5, 'pending', NULL, NULL, NULL)",
- params![
- tombstone.profile_id.as_str(),
- tombstone.target.as_str(),
- tombstone.project_key(),
- tombstone.tombstone_id.as_str(),
- tombstone.recorded_at_micros,
- ],
+ self.transact(async move |transaction| {
+ if let Some(existing) = read_tombstone(
+ &transaction,
+ &tombstone.profile_id,
+ tombstone.target,
+ tombstone.project_key(),
)
- .await
- .map_err(|error| remote_deletion_error("record remote deletion tombstone", error))?;
- transaction
- .commit()
- .await
- .map_err(|error| remote_deletion_error("commit remote deletion tombstone", error))?;
- Ok(RemoteDeletionTombstoneRecordOutcome::Recorded(tombstone))
+ .await?
+ {
+ return Ok(if existing.tombstone_id == tombstone.tombstone_id {
+ RemoteDeletionTombstoneRecordOutcome::Replayed(existing)
+ } else {
+ RemoteDeletionTombstoneRecordOutcome::Conflict { existing }
+ });
+ }
+ transaction
+ .execute(
+ "INSERT INTO remote_deletion_tombstones
+ (profile_id, target_kind, project_id, tombstone_id, recorded_at_micros,
+ cleanup_status, failure_code, failure_phase, retryable)
+ VALUES (?1, ?2, ?3, ?4, ?5, 'pending', NULL, NULL, NULL)",
+ params![
+ tombstone.profile_id.as_str(),
+ tombstone.target.as_str(),
+ tombstone.project_key(),
+ tombstone.tombstone_id.as_str(),
+ tombstone.recorded_at_micros,
+ ],
+ )
+ .await
+ .map_err(|error| {
+ remote_deletion_error("record remote deletion tombstone", error)
+ })?;
+ Ok(RemoteDeletionTombstoneRecordOutcome::Recorded(
+ tombstone.clone(),
+ ))
+ })
+ .await?
}
#[tracing::instrument(
@@ -376,70 +375,65 @@ impl RegisteredGlobalDb {
next: RemoteDeletionCleanupState,
) -> Result {
validate_transition(&next)?;
- let transaction = self.begin_write_transaction().await?;
- let existing = read_tombstone(
- &transaction,
- &tombstone.profile_id,
- tombstone.target,
- tombstone.project_key(),
- )
- .await?
- .ok_or_else(|| {
- remote_deletion_error(
- "transition remote deletion tombstone",
- "tombstone is unavailable",
- )
- })?;
- if existing.tombstone_id != tombstone.tombstone_id {
- transaction.commit().await.map_err(|error| {
- remote_deletion_error("commit remote deletion conflict read", error)
- })?;
- return Ok(RemoteDeletionTombstoneTransitionOutcome::Conflict { existing });
- }
- if existing.cleanup != expected {
- transaction.commit().await.map_err(|error| {
- remote_deletion_error("commit remote deletion state read", error)
- })?;
- return Ok(RemoteDeletionTombstoneTransitionOutcome::StateChanged { existing });
- }
- let (failure_code, failure_phase, retryable) =
- next.failure()
- .map_or((None, None, None), |(code, phase, retryable)| {
- (
- Some(code.as_str()),
- Some(phase.as_str()),
- Some(i64::from(retryable)),
- )
- });
- transaction
- .execute(
- "UPDATE remote_deletion_tombstones
- SET cleanup_status = ?1, failure_code = ?2, failure_phase = ?3, retryable = ?4
- WHERE profile_id = ?5 AND target_kind = ?6 AND project_id = ?7
- AND tombstone_id = ?8",
- params![
- next.status(),
- failure_code,
- failure_phase,
- retryable,
- tombstone.profile_id.as_str(),
- tombstone.target.as_str(),
- tombstone.project_key(),
- tombstone.tombstone_id.as_str(),
- ],
+ let tombstone = tombstone.clone();
+ let expected = expected.clone();
+ self.transact(async move |transaction| {
+ let existing = read_tombstone(
+ &transaction,
+ &tombstone.profile_id,
+ tombstone.target,
+ tombstone.project_key(),
)
- .await
- .map_err(|error| {
- remote_deletion_error("transition remote deletion tombstone", error)
+ .await?
+ .ok_or_else(|| {
+ remote_deletion_error(
+ "transition remote deletion tombstone",
+ "tombstone is unavailable",
+ )
})?;
- transaction.commit().await.map_err(|error| {
- remote_deletion_error("commit remote deletion tombstone transition", error)
- })?;
- let updated = RemoteDeletionTombstone {
- cleanup: next,
- ..existing
- };
- Ok(RemoteDeletionTombstoneTransitionOutcome::Updated(updated))
+ if existing.tombstone_id != tombstone.tombstone_id {
+ return Ok(RemoteDeletionTombstoneTransitionOutcome::Conflict { existing });
+ }
+ if existing.cleanup != expected {
+ return Ok(RemoteDeletionTombstoneTransitionOutcome::StateChanged { existing });
+ }
+ let (failure_code, failure_phase, retryable) =
+ next.failure()
+ .map_or((None, None, None), |(code, phase, retryable)| {
+ (
+ Some(code.as_str()),
+ Some(phase.as_str()),
+ Some(i64::from(retryable)),
+ )
+ });
+ transaction
+ .execute(
+ "UPDATE remote_deletion_tombstones
+ SET cleanup_status = ?1, failure_code = ?2, failure_phase = ?3, retryable = ?4
+ WHERE profile_id = ?5 AND target_kind = ?6 AND project_id = ?7
+ AND tombstone_id = ?8",
+ params![
+ next.status(),
+ failure_code,
+ failure_phase,
+ retryable,
+ tombstone.profile_id.as_str(),
+ tombstone.target.as_str(),
+ tombstone.project_key(),
+ tombstone.tombstone_id.as_str(),
+ ],
+ )
+ .await
+ .map_err(|error| {
+ remote_deletion_error("transition remote deletion tombstone", error)
+ })?;
+ let updated = RemoteDeletionTombstone {
+ cleanup: next.clone(),
+ ..existing
+ };
+ Ok(RemoteDeletionTombstoneTransitionOutcome::Updated(updated))
+ })
+ .await?
}
pub async fn remote_deletion_tombstone_for_project(
@@ -475,19 +469,20 @@ impl RegisteredGlobalDb {
pub async fn delete_remote_deleted_project_registry_row(&self, project_id: &str) -> Result<()> {
validate_identifier("remote deletion project id", project_id)?;
- let transaction = self.begin_write_transaction().await?;
- transaction
- .execute(
- "DELETE FROM code_projects WHERE project_id = ?1",
- params![project_id],
- )
- .await
- .map_err(|error| {
- remote_deletion_error("remove remote-deleted project registry row", error)
- })?;
- transaction.commit().await.map_err(|error| {
- remote_deletion_error("commit remote-deleted project registry row", error)
+ let project_id = project_id.to_owned();
+ self.transact(async move |transaction| {
+ transaction
+ .execute(
+ "DELETE FROM code_projects WHERE project_id = ?1",
+ params![project_id.as_str()],
+ )
+ .await
+ .map_err(|error| {
+ remote_deletion_error("remove remote-deleted project registry row", error)
+ })?;
+ Ok(())
})
+ .await?
}
}
From 68af603fdbd6e396ddad1fba3105e1d8b8836455 Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 16:14:18 +0000
Subject: [PATCH 13/89] refactor(global-db): run analytics writes as jobs
---
.../src/registered_analytics.rs | 454 +++++++++---------
1 file changed, 231 insertions(+), 223 deletions(-)
diff --git a/crates/tracedecay-global-db/src/registered_analytics.rs b/crates/tracedecay-global-db/src/registered_analytics.rs
index 609e51ecf1..468eef0bc4 100644
--- a/crates/tracedecay-global-db/src/registered_analytics.rs
+++ b/crates/tracedecay-global-db/src/registered_analytics.rs
@@ -3,7 +3,7 @@ use std::fmt::Write as _;
use std::future::Future;
use std::pin::Pin;
-use tracedecay_runtime_core::db::engine::{Value, opt_text};
+use tracedecay_runtime_core::db::engine::{Executor, Value, opt_text};
use super::{
AnalyticsEventInsert, AnalyticsEventQuery, AnalyticsEventRecord, AnalyticsHintCounts,
@@ -236,23 +236,19 @@ impl RegisteredGlobalDb {
if events.is_empty() {
return Ok(Vec::new());
}
- let transaction = self
- .begin_write_transaction()
- .await
- .map_err(|error| format!("failed to begin analytics append transaction: {error}"))?;
- let ids = match kind {
- AnalyticsAppendKind::General => {
- append_analytics_events_in_existing_tx(&transaction, events).await?
- }
- AnalyticsAppendKind::Observability => {
- append_observability_events_in_existing_tx(&transaction, events).await?
- }
- };
- transaction
- .commit()
- .await
- .map_err(|error| format!("failed to commit analytics append transaction: {error}"))?;
- Ok(ids)
+ let events = events.to_vec();
+ self.transact_or(
+ |error| format!("failed to run analytics append transaction: {error}"),
+ async move |transaction| match kind {
+ AnalyticsAppendKind::General => {
+ append_analytics_events_in_existing_tx(&transaction, &events).await
+ }
+ AnalyticsAppendKind::Observability => {
+ append_observability_events_in_existing_tx(&transaction, &events).await
+ }
+ },
+ )
+ .await
}
#[tracing::instrument(
@@ -323,52 +319,55 @@ impl RegisteredGlobalDb {
owner_fact_json,
delivery_envelope_json,
)?;
- let transaction = self
- .begin_write_transaction()
- .await
- .map_err(|error| format!("failed to begin observability outbox claim: {error}"))?;
- if let Some(stored) = read_outbox_record(&transaction, project_id, owner_event_id).await? {
- if stored.owner_fact_json != owner_fact_json {
- return Err("observability owner fact conflict".to_owned());
- }
- let outcome = if let Some(analytics_event_id) = stored.analytics_event_id {
- ObservabilityEmissionClaimV1::Settled {
- delivery_envelope_json: stored.delivery_envelope_json,
- analytics_event_id,
- }
- } else {
- ObservabilityEmissionClaimV1::Pending {
- delivery_envelope_json: stored.delivery_envelope_json,
+ let project_id = project_id.to_owned();
+ let owner_event_id = owner_event_id.to_owned();
+ let owner_fact_json = owner_fact_json.to_owned();
+ let delivery_envelope_json = delivery_envelope_json.to_owned();
+ self.transact_or(
+ |error| format!("failed to run observability outbox claim: {error}"),
+ async move |transaction| {
+ if let Some(stored) =
+ read_outbox_record(&transaction, &project_id, &owner_event_id).await?
+ {
+ if stored.owner_fact_json != owner_fact_json {
+ return Err("observability owner fact conflict".to_owned());
+ }
+ return Ok(
+ if let Some(analytics_event_id) = stored.analytics_event_id {
+ ObservabilityEmissionClaimV1::Settled {
+ delivery_envelope_json: stored.delivery_envelope_json,
+ analytics_event_id,
+ }
+ } else {
+ ObservabilityEmissionClaimV1::Pending {
+ delivery_envelope_json: stored.delivery_envelope_json,
+ }
+ },
+ );
}
- };
- transaction
- .commit()
- .await
- .map_err(|error| format!("failed to close observability outbox replay: {error}"))?;
- return Ok(outcome);
- }
- transaction
- .execute(
- "INSERT INTO observability_emission_outbox
- (project_id, owner_event_id, owner_fact_json,
- delivery_envelope_json, state, analytics_event_id)
- VALUES (?1, ?2, ?3, ?4, 'pending', NULL)",
- tracedecay_runtime_core::db::engine::params![
- project_id,
- owner_event_id,
- owner_fact_json,
- delivery_envelope_json
- ],
- )
- .await
- .map_err(|error| format!("failed to claim observability outbox event: {error}"))?;
- transaction
- .commit()
- .await
- .map_err(|error| format!("failed to commit observability outbox claim: {error}"))?;
- Ok(ObservabilityEmissionClaimV1::Claimed {
- delivery_envelope_json: delivery_envelope_json.to_owned(),
- })
+ transaction
+ .execute(
+ "INSERT INTO observability_emission_outbox
+ (project_id, owner_event_id, owner_fact_json,
+ delivery_envelope_json, state, analytics_event_id)
+ VALUES (?1, ?2, ?3, ?4, 'pending', NULL)",
+ tracedecay_runtime_core::db::engine::params![
+ project_id.as_str(),
+ owner_event_id.as_str(),
+ owner_fact_json.as_str(),
+ delivery_envelope_json.as_str()
+ ],
+ )
+ .await
+ .map_err(|error| {
+ format!("failed to claim observability outbox event: {error}")
+ })?;
+ Ok(ObservabilityEmissionClaimV1::Claimed {
+ delivery_envelope_json: delivery_envelope_json.clone(),
+ })
+ },
+ )
+ .await
}
/// CAS a pending delivery to its delayed-coverage representation.
@@ -391,59 +390,56 @@ impl RegisteredGlobalDb {
owner_fact_json,
delayed_delivery_envelope_json,
)?;
- let transaction = self
- .begin_write_transaction()
- .await
- .map_err(|error| format!("failed to begin observability outbox delay: {error}"))?;
- let stored = read_outbox_record(&transaction, project_id, owner_event_id)
- .await?
- .ok_or_else(|| "observability outbox event is unavailable".to_owned())?;
- if stored.owner_fact_json != owner_fact_json {
- return Err("observability owner fact conflict".to_owned());
- }
- if let Some(analytics_event_id) = stored.analytics_event_id {
- transaction
- .commit()
- .await
- .map_err(|error| format!("failed to close settled outbox delay: {error}"))?;
- return Ok(ObservabilityEmissionClaimV1::Settled {
- delivery_envelope_json: stored.delivery_envelope_json,
- analytics_event_id,
- });
- }
- if stored.delivery_envelope_json != expected_delivery_envelope_json {
- transaction
- .commit()
- .await
- .map_err(|error| format!("failed to close replayed outbox delay: {error}"))?;
- return Ok(ObservabilityEmissionClaimV1::Pending {
- delivery_envelope_json: stored.delivery_envelope_json,
- });
- }
- transaction
- .execute(
- "UPDATE observability_emission_outbox
- SET delivery_envelope_json = ?4
- WHERE project_id = ?1 AND owner_event_id = ?2
- AND owner_fact_json = ?3 AND state = 'pending'
- AND delivery_envelope_json = ?5",
- tracedecay_runtime_core::db::engine::params![
- project_id,
- owner_event_id,
- owner_fact_json,
- delayed_delivery_envelope_json,
- expected_delivery_envelope_json
- ],
- )
- .await
- .map_err(|error| format!("failed to mark observability delivery delayed: {error}"))?;
- transaction
- .commit()
- .await
- .map_err(|error| format!("failed to commit observability outbox delay: {error}"))?;
- Ok(ObservabilityEmissionClaimV1::Pending {
- delivery_envelope_json: delayed_delivery_envelope_json.to_owned(),
- })
+ let project_id = project_id.to_owned();
+ let owner_event_id = owner_event_id.to_owned();
+ let owner_fact_json = owner_fact_json.to_owned();
+ let expected_delivery_envelope_json = expected_delivery_envelope_json.to_owned();
+ let delayed_delivery_envelope_json = delayed_delivery_envelope_json.to_owned();
+ self.transact_or(
+ |error| format!("failed to run observability outbox delay: {error}"),
+ async move |transaction| {
+ let stored = read_outbox_record(&transaction, &project_id, &owner_event_id)
+ .await?
+ .ok_or_else(|| "observability outbox event is unavailable".to_owned())?;
+ if stored.owner_fact_json != owner_fact_json {
+ return Err("observability owner fact conflict".to_owned());
+ }
+ if let Some(analytics_event_id) = stored.analytics_event_id {
+ return Ok(ObservabilityEmissionClaimV1::Settled {
+ delivery_envelope_json: stored.delivery_envelope_json,
+ analytics_event_id,
+ });
+ }
+ if stored.delivery_envelope_json != expected_delivery_envelope_json {
+ return Ok(ObservabilityEmissionClaimV1::Pending {
+ delivery_envelope_json: stored.delivery_envelope_json,
+ });
+ }
+ transaction
+ .execute(
+ "UPDATE observability_emission_outbox
+ SET delivery_envelope_json = ?4
+ WHERE project_id = ?1 AND owner_event_id = ?2
+ AND owner_fact_json = ?3 AND state = 'pending'
+ AND delivery_envelope_json = ?5",
+ tracedecay_runtime_core::db::engine::params![
+ project_id.as_str(),
+ owner_event_id.as_str(),
+ owner_fact_json.as_str(),
+ delayed_delivery_envelope_json.as_str(),
+ expected_delivery_envelope_json.as_str()
+ ],
+ )
+ .await
+ .map_err(|error| {
+ format!("failed to mark observability delivery delayed: {error}")
+ })?;
+ Ok(ObservabilityEmissionClaimV1::Pending {
+ delivery_envelope_json: delayed_delivery_envelope_json.clone(),
+ })
+ },
+ )
+ .await
}
/// Append the exact delivery and settle its outbox row in one transaction.
@@ -472,48 +468,54 @@ impl RegisteredGlobalDb {
{
return Err("observability outbox delivery binding conflict".to_owned());
}
- let transaction = self
- .begin_write_transaction()
- .await
- .map_err(|error| format!("failed to begin observability outbox settlement: {error}"))?;
- let stored = read_outbox_record(&transaction, project_id, owner_event_id)
- .await?
- .ok_or_else(|| "observability outbox event is unavailable".to_owned())?;
- if stored.owner_fact_json != owner_fact_json
- || stored.delivery_envelope_json != delivery_envelope_json
- {
- return Err("observability outbox settlement conflict".to_owned());
- }
- let id = append_observability_event_in_existing_tx(&transaction, event)
- .await
- .map_err(|error| error.to_string())?;
- if let Some(settled_id) = stored.analytics_event_id {
- if settled_id != id {
- return Err("observability outbox receipt conflict".to_owned());
- }
- } else {
- transaction
- .execute(
- "UPDATE observability_emission_outbox
- SET state = 'settled', analytics_event_id = ?5
- WHERE project_id = ?1 AND owner_event_id = ?2
- AND owner_fact_json = ?3 AND delivery_envelope_json = ?4
- AND state = 'pending'",
- tracedecay_runtime_core::db::engine::params![
- project_id,
- owner_event_id,
- owner_fact_json,
- delivery_envelope_json,
- id
- ],
- )
- .await
- .map_err(|error| format!("failed to settle observability outbox event: {error}"))?;
- }
- transaction.commit().await.map_err(|error| {
- format!("failed to commit observability outbox settlement: {error}")
- })?;
- Ok(id)
+ let project_id = project_id.to_owned();
+ let owner_event_id = owner_event_id.to_owned();
+ let owner_fact_json = owner_fact_json.to_owned();
+ let delivery_envelope_json = delivery_envelope_json.to_owned();
+ let event = event.clone();
+ self.transact_or(
+ |error| format!("failed to run observability outbox settlement: {error}"),
+ async move |transaction| {
+ let stored = read_outbox_record(&transaction, &project_id, &owner_event_id)
+ .await?
+ .ok_or_else(|| "observability outbox event is unavailable".to_owned())?;
+ if stored.owner_fact_json != owner_fact_json
+ || stored.delivery_envelope_json != delivery_envelope_json
+ {
+ return Err("observability outbox settlement conflict".to_owned());
+ }
+ let id = append_observability_event_in_existing_tx(&transaction, &event)
+ .await
+ .map_err(|error| error.to_string())?;
+ if let Some(settled_id) = stored.analytics_event_id {
+ if settled_id != id {
+ return Err("observability outbox receipt conflict".to_owned());
+ }
+ } else {
+ transaction
+ .execute(
+ "UPDATE observability_emission_outbox
+ SET state = 'settled', analytics_event_id = ?5
+ WHERE project_id = ?1 AND owner_event_id = ?2
+ AND owner_fact_json = ?3 AND delivery_envelope_json = ?4
+ AND state = 'pending'",
+ tracedecay_runtime_core::db::engine::params![
+ project_id.as_str(),
+ owner_event_id.as_str(),
+ owner_fact_json.as_str(),
+ delivery_envelope_json.as_str(),
+ id
+ ],
+ )
+ .await
+ .map_err(|error| {
+ format!("failed to settle observability outbox event: {error}")
+ })?;
+ }
+ Ok(id)
+ },
+ )
+ .await
}
/// Claim and settle one run of worker-owned owner facts in a single write.
@@ -630,43 +632,43 @@ impl RegisteredGlobalDb {
if now_seconds < 0 {
return Err("invalid observability retention time".to_owned());
}
- let transaction = self
- .begin_write_transaction()
- .await
- .map_err(|error| format!("failed to begin observability retention: {error}"))?;
- // Expired dirty-day markers go first: they protect their source rows
- // from the analytics pass below, and a day past the horizon has no
- // generation left to rebuild.
- let expired_rollups = expire_observability_rollups(&transaction, now_seconds).await?;
- let (expired_detail_outbox, expired_detail) = prune_observability_retention_class(
- &transaction,
- now_seconds.saturating_sub(OBSERVABILITY_DETAIL_RETENTION_SECONDS),
- "optional_local_detail30d",
- )
- .await?;
- let (expired_rollup_outbox, expired_rollup) = prune_observability_retention_class(
- &transaction,
- now_seconds.saturating_sub(OBSERVABILITY_ROLLUP_RETENTION_SECONDS),
- "local_rollup395d",
- )
- .await?;
- let has_more = observability_retention_has_more(
- &transaction,
- now_seconds.saturating_sub(OBSERVABILITY_DETAIL_RETENTION_SECONDS),
- now_seconds.saturating_sub(OBSERVABILITY_ROLLUP_RETENTION_SECONDS),
+ self.transact_or(
+ |error| format!("failed to run observability retention: {error}"),
+ async move |transaction| {
+ // Expired dirty-day markers go first: they protect their source rows
+ // from the analytics pass below, and a day past the horizon has no
+ // generation left to rebuild.
+ let expired_rollups =
+ expire_observability_rollups(&transaction, now_seconds).await?;
+ let (expired_detail_outbox, expired_detail) = prune_observability_retention_class(
+ &transaction,
+ now_seconds.saturating_sub(OBSERVABILITY_DETAIL_RETENTION_SECONDS),
+ "optional_local_detail30d",
+ )
+ .await?;
+ let (expired_rollup_outbox, expired_rollup) = prune_observability_retention_class(
+ &transaction,
+ now_seconds.saturating_sub(OBSERVABILITY_ROLLUP_RETENTION_SECONDS),
+ "local_rollup395d",
+ )
+ .await?;
+ let has_more = observability_retention_has_more(
+ &transaction,
+ now_seconds.saturating_sub(OBSERVABILITY_DETAIL_RETENTION_SECONDS),
+ now_seconds.saturating_sub(OBSERVABILITY_ROLLUP_RETENTION_SECONDS),
+ )
+ .await?;
+ Ok(ObservabilityRetentionReceiptV1 {
+ expired_detail,
+ expired_rollup,
+ expired_settled_outbox: expired_detail_outbox
+ .saturating_add(expired_rollup_outbox),
+ expired_rollups,
+ has_more,
+ })
+ },
)
- .await?;
- transaction
- .commit()
- .await
- .map_err(|error| format!("failed to commit observability retention: {error}"))?;
- Ok(ObservabilityRetentionReceiptV1 {
- expired_detail,
- expired_rollup,
- expired_settled_outbox: expired_detail_outbox.saturating_add(expired_rollup_outbox),
- expired_rollups,
- has_more,
- })
+ .await
}
#[tracing::instrument(
@@ -701,22 +703,28 @@ impl RegisteredGlobalDb {
expected_cursor: super::ParseOffset,
cursor: super::ParseOffset,
) -> Result, String> {
- let transaction = self
- .begin_write_transaction()
- .await
- .map_err(|error| format!("failed to begin analytics import transaction: {error}"))?;
- super::transcript::require_expected_offset(&transaction, cursor_path, expected_cursor)
- .await
- .map_err(|error| format!("failed to claim analytics import cursor: {error}"))?;
- let ids = append_analytics_events_in_existing_tx(&transaction, events).await?;
- super::transcript::set_parse_offset(&transaction, cursor_path, cursor)
- .await
- .map_err(|error| format!("failed to persist analytics import cursor: {error}"))?;
- transaction
- .commit()
- .await
- .map_err(|error| format!("failed to commit analytics import transaction: {error}"))?;
- Ok(ids)
+ let events = events.to_vec();
+ let cursor_path = cursor_path.to_owned();
+ self.transact_or(
+ |error| format!("failed to run analytics import transaction: {error}"),
+ async move |transaction| {
+ super::transcript::require_expected_offset(
+ &transaction,
+ &cursor_path,
+ expected_cursor,
+ )
+ .await
+ .map_err(|error| format!("failed to claim analytics import cursor: {error}"))?;
+ let ids = append_analytics_events_in_existing_tx(&transaction, &events).await?;
+ super::transcript::set_parse_offset(&transaction, &cursor_path, cursor)
+ .await
+ .map_err(|error| {
+ format!("failed to persist analytics import cursor: {error}")
+ })?;
+ Ok(ids)
+ },
+ )
+ .await
}
#[tracing::instrument(
@@ -950,7 +958,7 @@ impl RegisteredGlobalDb {
}
async fn append_analytics_events_in_existing_tx(
- transaction: &super::RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl Executor,
events: &[AnalyticsEventInsert],
) -> Result, String> {
let mut ids = Vec::with_capacity(events.len());
@@ -1016,7 +1024,7 @@ async fn append_analytics_events_in_existing_tx(
}
async fn append_analytics_event_in_existing_tx(
- transaction: &super::RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl Executor,
event: &AnalyticsEventInsert,
) -> Result {
let mut rows = transaction
@@ -1098,7 +1106,7 @@ fn validate_prepared_owner_emission(
}
async fn claim_and_settle_owner_facts_in_tx(
- transaction: &RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl Executor,
emissions: &[ObservabilityOwnerEmissionWriteV1],
prepare_new: F,
) -> Result, String>
@@ -1131,7 +1139,7 @@ where
/// Resolves every still-open fact against the outbox and prepares a delivery
/// only for a fact no row owns yet.
async fn prepare_unclaimed_owner_facts(
- transaction: &RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl Executor,
emissions: &[ObservabilityOwnerEmissionWriteV1],
outcomes: &mut [Option],
mut prepare_new: F,
@@ -1183,7 +1191,7 @@ where
/// Adopts an exact stored event, appends the rest in one statement, and
/// settles their outbox rows in one statement.
async fn settle_prepared_owner_facts(
- transaction: &RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl Executor,
emissions: &[ObservabilityOwnerEmissionWriteV1],
outcomes: &mut [Option],
prepared: Vec<(usize, PreparedObservabilityEmissionV1)>,
@@ -1263,7 +1271,7 @@ fn bind_key_pairs(mut sql: String, keys: Vec<(String, String)>) -> (String, Vec<
/// Stored owner facts for `keys`, by `(project_id, owner_event_id)`.
async fn stored_owner_facts(
- transaction: &RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl Executor,
keys: Vec<(String, String)>,
) -> Result, String> {
let mut stored = BTreeMap::new();
@@ -1298,7 +1306,7 @@ async fn stored_owner_facts(
/// Stored canonical observability events for `keys`, by
/// `(project_id, hint_id)`.
async fn stored_observability_events(
- transaction: &RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl Executor,
keys: Vec<(String, String)>,
) -> Result, String> {
let mut stored = BTreeMap::new();
@@ -1336,7 +1344,7 @@ async fn stored_observability_events(
}
async fn insert_settled_outbox_rows(
- transaction: &RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl Executor,
rows: &[(&ObservabilityOwnerEmissionWriteV1, String, i64)],
) -> Result<(), String> {
for chunk in rows.chunks(ANALYTICS_INSERT_ROWS_PER_STATEMENT) {
@@ -1407,7 +1415,7 @@ fn validate_outbox_input(
}
async fn read_outbox_record(
- transaction: &RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl Executor,
project_id: &str,
owner_event_id: &str,
) -> Result, String> {
@@ -1454,7 +1462,7 @@ enum ObservabilityAppendError {
/// All runs share the caller's transaction, so a later conflict rolls back
/// earlier appends just as the single-event path does.
async fn append_observability_events_in_existing_tx(
- transaction: &RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl Executor,
events: &[AnalyticsEventInsert],
) -> Result, String> {
let mut ids = Vec::with_capacity(events.len());
@@ -1514,7 +1522,7 @@ async fn append_observability_events_in_existing_tx(
}
async fn append_observability_event_in_existing_tx(
- transaction: &RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl Executor,
event: &AnalyticsEventInsert,
) -> Result {
validate_observability_event(event).map_err(ObservabilityAppendError::Failed)?;
@@ -1583,7 +1591,7 @@ fn analytics_record_matches_insert(
}
async fn prune_observability_retention_class(
- transaction: &RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl Executor,
cutoff_seconds: i64,
retention_class: &str,
) -> Result<(u64, u64), String> {
@@ -1625,7 +1633,7 @@ async fn prune_observability_retention_class(
}
async fn observability_retention_has_more(
- transaction: &RegisteredGlobalDbWriteTransaction<'_>,
+ transaction: &impl Executor,
detail_cutoff_seconds: i64,
rollup_cutoff_seconds: i64,
) -> Result {
From f2f81a2598fe438307613a0fd53890f8c7125a2a Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 16:14:19 +0000
Subject: [PATCH 14/89] test(global-db): prove job exits and scope loss roll
back
---
crates/tracedecay-global-db/src/tests.rs | 86 ++++++++++++++++++++++++
1 file changed, 86 insertions(+)
diff --git a/crates/tracedecay-global-db/src/tests.rs b/crates/tracedecay-global-db/src/tests.rs
index 9e471a6fdd..8629cda561 100644
--- a/crates/tracedecay-global-db/src/tests.rs
+++ b/crates/tracedecay-global-db/src/tests.rs
@@ -1483,6 +1483,92 @@ async fn transact_rolls_back_when_scope_ends_before_commit() {
);
}
+#[asupersync::test]
+async fn transact_or_rollback_keeps_an_early_value_but_never_a_commit() {
+ use crate::registered::JobExit;
+ use tracedecay_runtime_core::db::engine::Executor as _;
+ let harness = RegisteredGlobalDbHarness::open("transact-or-rollback-exits").await;
+ let db = harness.registered.clone();
+
+ let replayed = db
+ .transact_or_rollback(
+ |error| error.to_string(),
+ async move |transaction| {
+ transaction
+ .execute("CREATE TABLE job_exit_rolled_back (value INTEGER)", ())
+ .await
+ .map_err(|error| error.to_string())?;
+ Err::(JobExit::Rollback(7))
+ },
+ )
+ .await;
+ assert_eq!(replayed, Ok(7));
+ assert!(
+ !table_exists(&db, "job_exit_rolled_back").await,
+ "an early exit left its writes behind"
+ );
+
+ let failed = db
+ .transact_or_rollback(
+ |error| error.to_string(),
+ async move |transaction| {
+ transaction
+ .execute("CREATE TABLE job_exit_failed (value INTEGER)", ())
+ .await
+ .map_err(|error| error.to_string())?;
+ Err::(JobExit::Fail("body failed".to_owned()))
+ },
+ )
+ .await;
+ assert_eq!(failed, Err("body failed".to_owned()));
+ assert!(!table_exists(&db, "job_exit_failed").await);
+}
+
+#[asupersync::test]
+async fn transact_or_wraps_a_scope_that_ended_mid_body_and_rolls_back() {
+ use tracedecay_runtime_core::db::engine::Executor as _;
+ let mut harness = RegisteredGlobalDbHarness::open("transact-or-scope-end").await;
+ let db = harness.registered.clone();
+ let scope = std::sync::Arc::new(std::sync::Mutex::new(harness.take_scope()));
+
+ let error = db
+ .transact_or(
+ |error| format!("wrapped: {error}"),
+ async move |transaction| {
+ transaction
+ .execute("CREATE TABLE transact_or_staged (value INTEGER)", ())
+ .await
+ .map_err(|error| error.to_string())?;
+ drop(scope.lock().unwrap().take());
+ Ok::<_, String>(())
+ },
+ )
+ .await
+ .expect_err("transact_or committed after the scope ended");
+
+ assert!(error.starts_with("wrapped: "), "{error}");
+ assert!(error.contains("active daemon"), "{error}");
+ assert!(
+ !table_exists(&db, "transact_or_staged").await,
+ "scope loss did not roll the job back"
+ );
+}
+
+#[asupersync::test]
+async fn converted_write_is_refused_without_a_write_scope() {
+ let mut harness = RegisteredGlobalDbHarness::open("converted-write-no-scope").await;
+ let db = harness.registered.clone();
+ harness.revoke();
+
+ let error = db
+ .try_record_savings("/project", "tool", 10, 4, 1)
+ .await
+ .expect_err("a converted write ran without a write scope");
+
+ assert!(error.to_string().contains("active daemon"), "{error}");
+ assert_eq!(row_count(&db, "savings_ledger").await, 0);
+}
+
#[asupersync::test]
async fn begun_registered_transaction_can_roll_back_after_scope_drop() {
let mut harness = RegisteredGlobalDbHarness::open("registered-rollback").await;
From 9afc4aa0a30a8c4960d40bba6f3c6471ab99025d Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 16:14:39 +0000
Subject: [PATCH 15/89] docs(storage): record step 5i-4 global-db job writes
---
docs/plans/storage/README.md | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/docs/plans/storage/README.md b/docs/plans/storage/README.md
index 021f5d860f..67e644e9a0 100644
--- a/docs/plans/storage/README.md
+++ b/docs/plans/storage/README.md
@@ -773,6 +773,24 @@ changes a file that holds memory.
clones what it owns into the closure. A rollup rebuild that finds its stored
receipt still ends by rolling back, so replay does not advance the transaction
generation.
+- Step 5i-4, global-db job writes (2026-10-18, branch
+ `refactor/storage-global-db-jobs-2`). The accounting, workflow-index, topology
+ anchor, discovery queue, GitHub stack delivery, delivery settlement, source
+ rewrite, registry forget and retire, remote deletion and analytics outbox writes
+ now run as `RegisteredGlobalDb::transact` jobs. `transact_or` maps a refused job
+ to the site's own error type. `transact_or_rollback` lets a body end early with
+ a value the caller wants as success, as a replayed append does, and the job
+ rolls back so a replay leaves no commit. Helpers under these sites take
+ `&impl Executor`, so one body serves the job and the remaining interactive
+ transactions. `WorkflowIngestWriteTxn` had no caller left and is deleted. Still
+ interactive, for later slices: `apply_registry_gc` (it reads outside the
+ transaction while holding it), `relink_orphan_store_instance` (a manifest file
+ effect sits between the SQL and the commit), `claim_and_settle_observability_emissions`
+ (its `FnMut` allocates producer sequences, so a re-run would allocate twice),
+ `append_analytics_event` (a writer connection, not an interactive transaction),
+ and the project registry, observation projection, LCM, provider usage,
+ dashboard, session, schema-stage and invariant, git-index and observation
+ retention and canonical-body groups.
- #4138 (2026-10-16). Shutdown store close waits up to 3 s for leases to drain
before deciding a store's WAL cannot be truncated. Under load, owners joined
before store close could still be unwinding the tasks that held their leases,
From 6ce5353a6038c908f4daf40d64b5e18361eed87a Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 16:17:58 +0000
Subject: [PATCH 16/89] chore: regenerate dashboard contracts without
upload_enabled
---
.../codegen/schemas/dashboard-contracts.schema.json | 10 ----------
dashboard/src/contracts/decoders.ts | 4 ----
dashboard/src/contracts/types.ts | 2 --
3 files changed, 16 deletions(-)
diff --git a/dashboard/codegen/schemas/dashboard-contracts.schema.json b/dashboard/codegen/schemas/dashboard-contracts.schema.json
index e65724f847..ee47a131a9 100644
--- a/dashboard/codegen/schemas/dashboard-contracts.schema.json
+++ b/dashboard/codegen/schemas/dashboard-contracts.schema.json
@@ -26581,12 +26581,6 @@
"idempotency_key": {
"type": "string"
},
- "upload_enabled": {
- "type": [
- "boolean",
- "null"
- ]
- },
"watcher_debounce": {
"type": [
"string",
@@ -26640,9 +26634,6 @@
},
"type": "array"
},
- "upload_enabled": {
- "type": "boolean"
- },
"watcher_debounce": {
"type": "string"
}
@@ -26652,7 +26643,6 @@
"configuration_revision_id",
"code_index_worker_configuration_snapshot_id",
"code_index_worker_configuration_revision_id",
- "upload_enabled",
"code_index_workers",
"code_index_worker_status",
"watcher_debounce",
diff --git a/dashboard/src/contracts/decoders.ts b/dashboard/src/contracts/decoders.ts
index 67b2c136d4..38737d6a67 100644
--- a/dashboard/src/contracts/decoders.ts
+++ b/dashboard/src/contracts/decoders.ts
@@ -9455,13 +9455,11 @@ export const UserSettingsPatchSchema: z.ZodObject<{
expected_revision_id: z.ZodType;
extraction_timeout_secs: z.ZodOptional>;
idempotency_key: z.ZodType;
- upload_enabled: z.ZodOptional>;
watcher_debounce: z.ZodOptional>;
}, z.core.$strict> = z.object({
expected_revision_id: z.string(),
extraction_timeout_secs: z.number().int().min(0).nullable().optional(),
idempotency_key: z.string(),
- upload_enabled: z.boolean().nullable().optional(),
watcher_debounce: z.string().nullable().optional(),
}).strict();
@@ -9474,7 +9472,6 @@ export const UserSettingsPayloadV1Schema: z.ZodObject<{
configuration_snapshot_id: z.ZodType;
extraction_timeout_secs: z.ZodType;
installed_agents: z.ZodType, unknown>;
- upload_enabled: z.ZodType;
watcher_debounce: z.ZodType;
}> = z.object({
code_index_worker_configuration_revision_id: z.string(),
@@ -9485,7 +9482,6 @@ export const UserSettingsPayloadV1Schema: z.ZodObject<{
configuration_snapshot_id: z.string(),
extraction_timeout_secs: z.number().int().min(0),
installed_agents: z.array(z.string()),
- upload_enabled: z.boolean(),
watcher_debounce: z.string(),
});
diff --git a/dashboard/src/contracts/types.ts b/dashboard/src/contracts/types.ts
index 7eceeb6733..b9e3fcaa73 100644
--- a/dashboard/src/contracts/types.ts
+++ b/dashboard/src/contracts/types.ts
@@ -5423,7 +5423,6 @@ export type UserSettingsPatch = {
expected_revision_id: string;
extraction_timeout_secs?: number | null | undefined;
idempotency_key: string;
- upload_enabled?: boolean | null | undefined;
watcher_debounce?: string | null | undefined;
};
@@ -5436,7 +5435,6 @@ export type UserSettingsPayloadV1 = {
configuration_snapshot_id: string;
extraction_timeout_secs: number;
installed_agents: Array;
- upload_enabled: boolean;
watcher_debounce: string;
};
From 95381c8ae9b7a722cefc97c7c0b4184a32b840bb Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 16:31:16 +0000
Subject: [PATCH 17/89] build(evals): resolve eval binary through the shipping
Bazel target
---
docs/memory-evals.md | 4 +-
evals/agent_adoption/README.md | 17 +++--
evals/agent_adoption/run.sh | 41 +++---------
evals/hermetic/README.md | 28 +++++---
evals/hermetic/run.sh | 47 ++-----------
evals/memory/run_real_model.py | 32 ++++++---
evals/memory/test_run_real_model.py | 27 ++++++++
scripts/BUILD.bazel | 8 +++
scripts/build-eval-binary.sh | 58 ++++++++++++++++
scripts/test-build-eval-binary.py | 100 ++++++++++++++++++++++++++++
10 files changed, 262 insertions(+), 100 deletions(-)
create mode 100755 scripts/build-eval-binary.sh
create mode 100644 scripts/test-build-eval-binary.py
diff --git a/docs/memory-evals.md b/docs/memory-evals.md
index 67bdc14d70..ff0502f977 100644
--- a/docs/memory-evals.md
+++ b/docs/memory-evals.md
@@ -117,7 +117,7 @@ actually determines whether durable memory helps a user. That is the
### How it works
-`evals/hermetic/run.sh` builds the dev binary, stages it at a non-cargo path,
+`evals/hermetic/run.sh` builds the shipping Bazel binary, stages a copy under the env dir,
installs the plugin into a throwaway `CLAUDE_CONFIG_DIR`/`CODEX_HOME`/
`TRACEDECAY_DATA_DIR`, indexes a project, then drives a **real** agent
(`claude -p` or `codex exec --json`) at each corpus prompt and records the
@@ -128,7 +128,7 @@ each session used. The adoption corpus is
per bucket.
```bash
-ENV=$(evals/hermetic/run.sh setup --agent codex --debug) # subscription; no API
+ENV=$(evals/hermetic/run.sh setup --agent codex) # subscription; no API
evals/hermetic/run.sh index --env-dir "$ENV" --project
evals/hermetic/run.sh run --agent codex --env-dir "$ENV" \
--corpus evals/hermetic/corpora/fact-store-adoption.jsonl
diff --git a/evals/agent_adoption/README.md b/evals/agent_adoption/README.md
index 6844d7cf17..cd1697c3fa 100644
--- a/evals/agent_adoption/README.md
+++ b/evals/agent_adoption/README.md
@@ -88,7 +88,8 @@ paths, so do **not** commit run artifacts.
| `PARALLEL` | `1` | concurrent live agent runs; scenarios are read-only against shared fixtures and each `claude` process spawns its own MCP server, so `4` is a safe default for high-rep cells |
| `REPEATED_QUERY_HINTS` | `off` | `on` turns on the profile setting `user.repeated_query_hints.v1` for the eval's isolated profile before any agent launches, and suffixes transcripts with `__qhints`; see "Repeated-query hints on vs off" |
| `REAL_CLAUDE_DIR` | `$CLAUDE_CONFIG_DIR` or `~/.claude` | where the Claude credential comes from: its `.claude_code_oauth_token` and `settings.json` `env`/`model` keys; see "Store isolation & auth" |
-| `TRACEDECAY_BIN` | candidate branch (live) | explicit tracedecay binary override |
+| `TRACEDECAY_BIN` | Bazel build of this checkout | explicit prebuilt tracedecay binary; skips the build and must exist |
+| `TRACEDECAY_EVAL_BAZEL_CONFIG` | `release` | Bazel config for the build; `dist` builds the exact release bytes |
| `EVAL_OUT` | unset | dir to copy scoreboard + report into |
| `LEARNED_SKILL_WORKFLOW` | unset | run only that workflow's held-out scenarios, once per variant, on `full` |
| `LEARNED_SKILL_VARIANTS` | `none generated manual` | learned-skill variants to compare |
@@ -128,11 +129,15 @@ fails, and Codex drops the graph server without any transcript error. Name the m
model that actually answered; an alias resolved through
`ANTHROPIC_DEFAULT_*_MODEL` would be mislabelled.
-Live runs build a debug `tracedecay` binary from the checked-out candidate
-branch with ordinary Cargo and use it for fixture setup, MCP, hooks, and the
-throwaway Codex plugin install.
-`TRACEDECAY_BIN` pins a previously built candidate or release binary and avoids
-the runner build path. The daemon also receives a disposable HOME, XDG paths,
+Every run, dry or live, builds the shipping target
+`//crates/tracedecay-cli:tracedecay` from the checked-out candidate branch with
+`bazel build --config=release` (`scripts/build-eval-binary.sh`) and uses it for
+fixture setup, MCP, hooks, and the throwaway Codex plugin install. The runner
+prints the binary path, version, and sha256. A failed build stops the run; no
+older or PATH binary is substituted. Set `TRACEDECAY_EVAL_BAZEL_CONFIG=dist` to
+build the exact release bytes (thin LTO, stripped).
+`TRACEDECAY_BIN` pins a previously built candidate or release binary, which must
+exist, and skips the build; its identity line is printed the same way. The daemon also receives a disposable HOME, XDG paths,
host-config paths, temporary directory, and working directory. Only explicit
read-only authentication sources are carried across that boundary. Every
Claude condition, including `full`, loads the candidate `plugin/` via
diff --git a/evals/agent_adoption/run.sh b/evals/agent_adoption/run.sh
index c85245e7db..21d4d96309 100755
--- a/evals/agent_adoption/run.sh
+++ b/evals/agent_adoption/run.sh
@@ -43,7 +43,11 @@
# read-only against shared fixtures, so this is safe
# REPEATED_QUERY_HINTS on|off: the eval profile's repeated-query hint setting
# (default: off); on suffixes transcripts with __qhints
-# TRACEDECAY_BIN tracedecay binary (default: resolve from PATH)
+# TRACEDECAY_BIN prebuilt tracedecay binary to run instead of the Bazel
+# build (default: bazel build --config=release
+# //crates/tracedecay-cli:tracedecay)
+# TRACEDECAY_EVAL_BAZEL_CONFIG Bazel config for that build (default: release;
+# dist builds the exact release bytes)
# EVAL_OUT directory to also copy scoreboard.json + report.md into
# LEARNED_SKILL_WORKFLOW run only learned_skills/'s held-out scenarios,
# once per skill variant, on the full condition
@@ -111,36 +115,11 @@ STEER_TEXT="This repository has indexed code-relationship evidence available. Ch
live=0
if [[ "${TRACEDECAY_AGENT_EVALS:-}" == "1" ]]; then live=1; fi
-# Live runs default to the candidate branch binary. TRACEDECAY_BIN remains an
-# explicit override for release-binary comparisons; dry runs reuse a built
-# candidate when present and otherwise fall back to PATH.
-if [[ -n "${TRACEDECAY_BIN:-}" ]]; then
- TD="$TRACEDECAY_BIN"
-elif [[ "$live" == "1" ]]; then
- echo "building candidate tracedecay binary..."
- if [[ -n "${CARGO_TARGET_DIR:-}" ]]; then
- eval_target="$CARGO_TARGET_DIR"
- elif [[ -d /fast/cargo-target && -w /fast/cargo-target ]]; then
- eval_target="/fast/cargo-target/tracedecay-agent-adoption-evals"
- else
- eval_target="$repo_root/target"
- fi
- (
- cd "$repo_root"
- CARGO_TARGET_DIR="$eval_target" \
- cargo build --quiet --package tracedecay-cli --bin tracedecay
- )
- TD="$eval_target/debug/tracedecay"
-elif [[ -x "$repo_root/target/debug/tracedecay" ]]; then
- TD="$repo_root/target/debug/tracedecay"
-else
- TD="$(command -v tracedecay || true)"
-fi
-if [[ -z "$TD" || ! -x "$TD" ]]; then
- echo "error: tracedecay binary not found; set TRACEDECAY_BIN" >&2
- exit 2
-fi
-TD="$(cd "$(dirname "$TD")" && pwd)/$(basename "$TD")"
+# Live and dry runs both use the shipping Bazel binary built from this checkout
+# (scripts/build-eval-binary.sh). TRACEDECAY_BIN selects an already-built
+# artifact, such as a release binary, and must exist. A failed build exits;
+# no PATH or older-build fallback exists.
+TD="$("$repo_root/scripts/build-eval-binary.sh")" || exit 2
# Neutrality lint (USER DOCTRINE): fail fast, before building fixtures or
# spending a single token, if any scenario prompt names tracedecay/MCP/a
diff --git a/evals/hermetic/README.md b/evals/hermetic/README.md
index e8bb5a58d0..61cb95ce0c 100644
--- a/evals/hermetic/README.md
+++ b/evals/hermetic/README.md
@@ -8,11 +8,11 @@ sessions depend on the real `~/.claude`, `~/.tracedecay`, and
```bash
# One-shot: build + isolate + install + index + one trivial scenario.
-evals/hermetic/run.sh smoke --agent claude --model sonnet --debug
-evals/hermetic/run.sh smoke --agent codex --debug
+evals/hermetic/run.sh smoke --agent claude --model sonnet
+evals/hermetic/run.sh smoke --agent codex
# Full corpus against a reusable env:
-ENV=$(evals/hermetic/run.sh setup --agent claude --debug)
+ENV=$(evals/hermetic/run.sh setup --agent claude)
evals/hermetic/run.sh index --env-dir "$ENV" --project /path/to/repo
evals/hermetic/run.sh run --agent claude --env-dir "$ENV" --corpus my-corpus.jsonl --model sonnet
evals/hermetic/run.sh teardown --env-dir "$ENV"
@@ -22,6 +22,14 @@ Without `--project`, the harness indexes the checkout containing `run.sh`, not
the caller's working directory or a machine-specific primary checkout. Pass
`--project` explicitly to evaluate another repository.
+`setup` and `smoke` build the shipping target `//crates/tracedecay-cli:tracedecay`
+through `scripts/build-eval-binary.sh` (`bazel build --config=release`; set
+`TRACEDECAY_EVAL_BAZEL_CONFIG=dist` for the exact release bytes). The script
+prints the binary's version and sha256. A failed build aborts the run; there is
+no stale-binary fallback. `TRACEDECAY_BIN=` stages an already-built
+binary instead (for example a release binary to compare against) and the same
+identity line records which one ran.
+
Offline harness path-selection checks (no build, agent, or daemon required):
```bash
@@ -147,12 +155,12 @@ Each scored row in `results.jsonl` also records derived fields: **`verify_pass`*
The args-ergonomics corpus compares MCP-first behavior with CLI fallback:
```bash
-ENV=$(evals/hermetic/run.sh setup --agent claude --debug)
+ENV=$(evals/hermetic/run.sh setup --agent claude)
evals/hermetic/run.sh index --env-dir "$ENV" --project /path/to/tracedecay-worktree
evals/hermetic/run.sh run --agent claude --env-dir "$ENV" \
--corpus evals/hermetic/corpora/tool-args-ergonomics.jsonl --model sonnet
-ENV=$(evals/hermetic/run.sh setup --agent codex --debug)
+ENV=$(evals/hermetic/run.sh setup --agent codex)
evals/hermetic/run.sh index --env-dir "$ENV" --project /path/to/tracedecay-worktree
evals/hermetic/run.sh run --agent codex --env-dir "$ENV" \
--corpus evals/hermetic/corpora/tool-args-ergonomics.jsonl
@@ -175,17 +183,15 @@ scenario) and `summary.md` (pass count + per-scenario table).
- **Codex auth.** Codex mode copies only `~/.codex/auth.json` into the isolated
`CODEX_HOME`. If Codex changes auth storage, `codex exec` will fail closed in
the eval env instead of mutating the user's real config.
-- **Global cargo caches** (`~/.cargo/registry`) are shared, only the *output*
- (`CARGO_TARGET_DIR=/target`) is worktree-scoped. The build cannot
- corrupt the user's install because it never writes to `~/.cargo/bin`.
+- **The build cache** is Bazel's, shared across worktrees. The build cannot
+ corrupt the user's install because it only stages a copy under the env dir.
## Post-merge re-eval protocol
After the plugin-suite changes merge, re-run to compare against the baseline
facts recorded in tracedecay memory:
-1. **Rebuild** from the merged checkout: `run.sh setup` (drop `--debug` for a
- representative release build).
+1. **Rebuild** from the merged checkout: `run.sh setup`.
2. **Re-index** the same target project into the fresh env.
3. **Rerun the same corpus** at the same `--model`.
4. **Compare** the new `summary.md` pass rate and per-scenario tracedecay-vs-
@@ -212,7 +218,7 @@ how close the agent is to the intended loop.
Run recipe (adapted from the reusable-env examples above):
```bash
-ENV=$(evals/hermetic/run.sh setup --agent claude --debug)
+ENV=$(evals/hermetic/run.sh setup --agent claude)
evals/hermetic/run.sh index --env-dir "$ENV" --project /path/to/repo
evals/hermetic/run.sh run --agent claude --env-dir "$ENV" \
--corpus evals/hermetic/corpora/fact-store-adoption.jsonl --model sonnet
diff --git a/evals/hermetic/run.sh b/evals/hermetic/run.sh
index fe41caf708..b91391fcd2 100755
--- a/evals/hermetic/run.sh
+++ b/evals/hermetic/run.sh
@@ -9,7 +9,7 @@
# See ./README.md for the design, isolation guarantees, and limitations.
#
# Subcommands:
-# build Build (or reuse) the dev binary and stage it at a stable path.
+# build Build the shipping Bazel binary and stage it at a stable path.
# setup Build + create an isolated env dir + install the dev plugin into it.
# index Index a target project with the dev binary (into the isolated home).
# run Execute a corpus JSONL against the isolated env and score it.
@@ -51,42 +51,11 @@ die() { printf '[hermetic] ERROR: %s\n' "$*" >&2; exit 1; }
# * current_exe (the staged copy) is baked into hook commands, and
# * the MCP server command "tracedecay" resolves to the staged copy via PATH.
-# Build the dev binary from THIS worktree. Echoes the built artifact path.
+# Resolve the dev binary: the shipping Bazel target built from THIS worktree,
+# or an explicit TRACEDECAY_BIN. A failed build aborts; an older artifact is
+# never staged in its place. Echoes the binary path.
build_binary() {
- local profile_dir="release"
- local -a cargo_args=(build --release --package tracedecay-cli --bin tracedecay)
- if [[ "${BUILD_DEBUG:-0}" == "1" ]]; then
- profile_dir="debug"
- cargo_args=(build --package tracedecay-cli --bin tracedecay)
- fi
-
- # Isolate build artifacts from the user's normal target dir so a concurrent
- # `cargo` in this worktree is not disturbed and so is_cargo_target_binary can
- # recognise the location deterministically.
- local target_dir="${WORKTREE_ROOT}/target"
- log "building dev binary (${profile_dir}) from ${WORKTREE_ROOT}"
- # CRITICAL: a cargo failure must abort the whole run. Otherwise a stale
- # artifact from a previous build would be silently staged and the eval would
- # run against the WRONG binary -- exactly the failure this harness prevents.
- # We check cargo's exit status explicitly (a bare subshell in a command-sub
- # is not always fatal under set -e), and unless HERMETIC_ALLOW_STALE=1 we
- # refuse to proceed on a build failure even if an old artifact exists.
- local rc=0
- (
- cd "${WORKTREE_ROOT}"
- CARGO_TARGET_DIR="${target_dir}" cargo "${cargo_args[@]}" >&2
- ) || rc=$?
- if [[ "${rc}" -ne 0 ]]; then
- if [[ "${HERMETIC_ALLOW_STALE:-0}" == "1" ]]; then
- log "WARNING: cargo build failed (rc=${rc}); HERMETIC_ALLOW_STALE=1 set, reusing prior artifact"
- else
- die "cargo build failed (rc=${rc}); refusing to run against a possibly stale binary. Set HERMETIC_ALLOW_STALE=1 only if you understand the artifact may be old."
- fi
- fi
-
- local built="${target_dir}/${profile_dir}/tracedecay"
- [[ -x "${built}" ]] || die "expected built binary not found at ${built}"
- printf '%s\n' "${built}"
+ "${WORKTREE_ROOT}/scripts/build-eval-binary.sh"
}
# Copy the built binary to /bin/tracedecay (a stable non-cargo path).
@@ -594,11 +563,10 @@ Common options:
--corpus Corpus file for `run`.
--model Model override (default: sonnet for claude; gpt-5.6-sol for codex).
--reps Re-run the corpus N times (default: 1; `run` only).
- --debug Reuse/produce a debug build instead of release (faster).
Examples:
- run.sh smoke --agent claude --debug
- run.sh setup --agent codex --debug
+ run.sh smoke --agent claude
+ run.sh setup --agent codex
run.sh run --agent claude --env-dir /tmp/eval-env-... --corpus my-corpus.jsonl --model sonnet
EOF
}
@@ -617,7 +585,6 @@ main() {
--corpus) corpus="$2"; shift 2 ;;
--model) model="$2"; shift 2 ;;
--reps) reps="$2"; shift 2 ;;
- --debug) export BUILD_DEBUG=1; shift ;;
-h|--help) usage; exit 0 ;;
*) die "unknown option: $1" ;;
esac
diff --git a/evals/memory/run_real_model.py b/evals/memory/run_real_model.py
index b4aa1d363e..f966448a81 100755
--- a/evals/memory/run_real_model.py
+++ b/evals/memory/run_real_model.py
@@ -12,7 +12,7 @@
Without both flags the runner records a blocked report and exits with code 2.
The deterministic no-LLM layer lives in `tests/memory_eval_test.rs` and runs
-as part of the normal cargo test suite. CI never calls this script.
+as part of the normal Bazel test suite. CI never calls this script.
Example:
python3 evals/memory/run_real_model.py --scenario memory-no-pollution \
@@ -104,7 +104,10 @@ def parse_args(argv):
parser.add_argument(
"--tracedecay-bin",
type=Path,
- help="tracedecay binary (default: target/debug/tracedecay if built, else PATH).",
+ help=(
+ "prebuilt tracedecay binary (default: bazel build --config=release "
+ "//crates/tracedecay-cli:tracedecay)."
+ ),
)
args = parser.parse_args(argv)
if args.model is None:
@@ -160,15 +163,24 @@ def create_eval_environment(scenario_id):
def resolve_tracedecay_bin(explicit):
+ """The shipping Bazel binary, built fresh, or the explicit one if it exists.
+
+ scripts/build-eval-binary.sh owns both paths; it prints the binary's version
+ and sha256 on stderr and fails rather than reuse an older build.
+ """
+ env = dict(os.environ)
if explicit:
- return str(explicit)
- debug_bin = REPO_ROOT / "target/debug/tracedecay"
- if debug_bin.exists():
- return str(debug_bin)
- found = shutil.which("tracedecay")
- if not found:
- sys.exit("no tracedecay binary: build target/debug or install one on PATH")
- return found
+ env["TRACEDECAY_BIN"] = str(explicit)
+ result = subprocess.run(
+ [str(REPO_ROOT / "scripts/build-eval-binary.sh")],
+ env=env,
+ stdout=subprocess.PIPE,
+ text=True,
+ check=False,
+ )
+ if result.returncode != 0:
+ sys.exit(f"no tracedecay binary: scripts/build-eval-binary.sh exited {result.returncode}")
+ return result.stdout.strip()
def load_scenarios(ids):
diff --git a/evals/memory/test_run_real_model.py b/evals/memory/test_run_real_model.py
index 03d7a87d07..a171e039b4 100644
--- a/evals/memory/test_run_real_model.py
+++ b/evals/memory/test_run_real_model.py
@@ -71,6 +71,33 @@ def test_install_uses_the_isolated_user_hermes_home(self):
self.assertEqual(run_mock.call_args.kwargs["env"], env.env)
+class BinaryResolutionTest(unittest.TestCase):
+ def test_explicit_binary_is_used_and_must_exist(self):
+ with tempfile.TemporaryDirectory() as tmp:
+ binary = Path(tmp) / "tracedecay"
+ binary.write_text("#!/bin/sh\necho tracedecay 0.0.0\n")
+ binary.chmod(0o755)
+ with contextlib.redirect_stderr(io.StringIO()):
+ self.assertEqual(
+ run_real_model.resolve_tracedecay_bin(binary), str(binary)
+ )
+ with self.assertRaises(SystemExit):
+ run_real_model.resolve_tracedecay_bin(Path(tmp) / "missing")
+
+ def test_default_never_falls_back_to_a_prebuilt_or_path_binary(self):
+ failed = subprocess.CompletedProcess([], 2, stdout="")
+ with tempfile.TemporaryDirectory() as tmp:
+ path_binary = Path(tmp) / "tracedecay"
+ path_binary.write_text("#!/bin/sh\n")
+ path_binary.chmod(0o755)
+ with (
+ mock.patch.dict("os.environ", {"PATH": tmp}),
+ mock.patch.object(run_real_model.subprocess, "run", return_value=failed),
+ self.assertRaises(SystemExit),
+ ):
+ run_real_model.resolve_tracedecay_bin(None)
+
+
class CursorComposerSelectionTest(unittest.TestCase):
def test_cursor_driver_loads_the_installed_plugin_in_composer(self):
env = run_real_model.create_eval_environment("cursor-composer")
diff --git a/scripts/BUILD.bazel b/scripts/BUILD.bazel
index 9c43205848..67aaa9ed2e 100644
--- a/scripts/BUILD.bazel
+++ b/scripts/BUILD.bazel
@@ -43,6 +43,14 @@ py_test(
main = "test-check-sqlite-plan-stability.py",
)
+py_test(
+ name = "build_eval_binary_test",
+ size = "small",
+ srcs = ["test-build-eval-binary.py"],
+ data = ["build-eval-binary.sh"],
+ main = "test-build-eval-binary.py",
+)
+
py_test(
name = "ambient_tokio_check_test",
size = "small",
diff --git a/scripts/build-eval-binary.sh b/scripts/build-eval-binary.sh
new file mode 100755
index 0000000000..cdca2ebbda
--- /dev/null
+++ b/scripts/build-eval-binary.sh
@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+# Resolve the tracedecay binary an eval runs against and print its absolute path
+# on stdout. Every eval harness (evals/agent_adoption, evals/hermetic,
+# evals/memory) calls this, so they all execute the shipping target
+# //crates/tracedecay-cli:tracedecay with the same features and dependency
+# patches the release builds.
+#
+# Default: build that target with Bazel. A failed build is an error; no older
+# binary is ever substituted. TRACEDECAY_BIN selects an already-built artifact
+# (for example a release binary to compare against); it must exist and be
+# executable.
+#
+# Env:
+# TRACEDECAY_BIN explicit binary; skips the build
+# TRACEDECAY_EVAL_BAZEL_CONFIG Bazel config to build with (default: release;
+# `dist` adds thin LTO and symbol stripping to
+# build the exact release bytes)
+#
+# The selected artifact's version and sha256 go to stderr.
+set -euo pipefail
+
+repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
+target="//crates/tracedecay-cli:tracedecay"
+config="${TRACEDECAY_EVAL_BAZEL_CONFIG:-release}"
+build_cmd="bazel build --config=$config $target"
+
+fail() {
+ echo "error: $*" >&2
+ exit 2
+}
+
+if [[ -n "${TRACEDECAY_BIN:-}" ]]; then
+ [[ -f "$TRACEDECAY_BIN" && -x "$TRACEDECAY_BIN" ]] ||
+ fail "TRACEDECAY_BIN=$TRACEDECAY_BIN is not an executable file; unset it to build the default with: $build_cmd"
+ bin="$(cd "$(dirname "$TRACEDECAY_BIN")" && pwd)/$(basename "$TRACEDECAY_BIN")"
+ origin="explicit TRACEDECAY_BIN"
+else
+ command -v bazel >/dev/null 2>&1 ||
+ fail "bazel is required to build the eval binary (run: $build_cmd) or set TRACEDECAY_BIN"
+ echo "building eval binary: $build_cmd" >&2
+ (cd "$repo_root" && bazel build --config="$config" "$target") >&2 ||
+ fail "eval binary build failed; not reusing any older binary. Reproduce with: $build_cmd"
+ bazel_bin="$(cd "$repo_root" && bazel info --config="$config" bazel-bin)" ||
+ fail "could not resolve the Bazel output directory (bazel info --config=$config bazel-bin)"
+ bin="$bazel_bin/crates/tracedecay-cli/tracedecay"
+ [[ -f "$bin" && -x "$bin" ]] ||
+ fail "expected a binary at $bin after: $build_cmd"
+ origin="bazel --config=$config $target"
+fi
+
+if command -v sha256sum >/dev/null 2>&1; then
+ digest="$(sha256sum "$bin")"
+else
+ digest="$(shasum -a 256 "$bin")"
+fi
+version="$("$bin" --version 2>/dev/null | head -1)" || version=unknown
+echo "eval binary: $bin ($origin; ${version:-unknown}; sha256 ${digest%% *})" >&2
+printf '%s\n' "$bin"
diff --git a/scripts/test-build-eval-binary.py b/scripts/test-build-eval-binary.py
new file mode 100644
index 0000000000..22de025f37
--- /dev/null
+++ b/scripts/test-build-eval-binary.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python3
+"""build-eval-binary.sh builds the shipping target and never reuses a stale binary."""
+
+import os
+import shutil
+import stat
+import subprocess
+import tempfile
+import unittest
+from pathlib import Path
+
+SCRIPT = Path(__file__).with_name("build-eval-binary.sh")
+
+# Fake bazel: records its argv; `build` exits $FAKE_BUILD_RC, `info` prints $FAKE_BAZEL_BIN.
+FAKE_BAZEL = """#!/bin/sh
+echo "$@" >> "$FAKE_BAZEL_LOG"
+case "$1" in
+ build) exit "${FAKE_BUILD_RC:-0}" ;;
+ info) echo "$FAKE_BAZEL_BIN" ;;
+esac
+"""
+
+
+class BuildEvalBinaryTest(unittest.TestCase):
+ def setUp(self):
+ tmp = tempfile.TemporaryDirectory()
+ self.addCleanup(tmp.cleanup)
+ root = Path(tmp.name)
+ self.repo = root / "repo"
+ (self.repo / "scripts").mkdir(parents=True)
+ shutil.copy(SCRIPT, self.repo / "scripts" / SCRIPT.name)
+ self.bazel_bin = root / "bazel-bin"
+ cli = self.bazel_bin / "crates" / "tracedecay-cli"
+ cli.mkdir(parents=True)
+ # A binary from an earlier build, present before the build runs.
+ self.built = cli / "tracedecay"
+ self.built.write_text("#!/bin/sh\necho tracedecay 1.2.3\n")
+ self.built.chmod(0o755)
+ self.log = root / "bazel.log"
+ fakes = root / "fakes"
+ fakes.mkdir()
+ bazel = fakes / "bazel"
+ bazel.write_text(FAKE_BAZEL)
+ bazel.chmod(bazel.stat().st_mode | stat.S_IXUSR)
+ self.env = {
+ "PATH": f"{fakes}:{os.environ['PATH']}",
+ "FAKE_BAZEL_LOG": str(self.log),
+ "FAKE_BAZEL_BIN": str(self.bazel_bin),
+ }
+
+ def run_script(self, **env):
+ return subprocess.run(
+ ["bash", str(self.repo / "scripts" / SCRIPT.name)],
+ env={**self.env, **env},
+ text=True,
+ capture_output=True,
+ check=False,
+ )
+
+ def test_builds_the_shipping_target_and_prints_its_identity(self):
+ result = self.run_script()
+ self.assertEqual(result.returncode, 0, result.stderr)
+ self.assertEqual(result.stdout.strip(), str(self.built))
+ self.assertIn(
+ "build --config=release //crates/tracedecay-cli:tracedecay",
+ self.log.read_text(),
+ )
+ self.assertIn("tracedecay 1.2.3", result.stderr)
+ self.assertIn("sha256 ", result.stderr)
+
+ def test_config_override_reaches_bazel(self):
+ result = self.run_script(TRACEDECAY_EVAL_BAZEL_CONFIG="dist")
+ self.assertEqual(result.returncode, 0, result.stderr)
+ self.assertIn("build --config=dist //crates", self.log.read_text())
+
+ def test_failed_build_does_not_fall_back_to_the_stale_binary(self):
+ result = self.run_script(FAKE_BUILD_RC="1")
+ self.assertEqual(result.returncode, 2)
+ self.assertEqual(result.stdout, "")
+ self.assertIn("bazel build --config=release //crates/tracedecay-cli:tracedecay", result.stderr)
+
+ def test_explicit_binary_skips_the_build(self):
+ explicit = self.built.with_name("candidate")
+ explicit.write_text("#!/bin/sh\necho tracedecay 9.9.9\n")
+ explicit.chmod(0o755)
+ result = self.run_script(TRACEDECAY_BIN=str(explicit))
+ self.assertEqual(result.returncode, 0, result.stderr)
+ self.assertEqual(result.stdout.strip(), str(explicit))
+ self.assertIn("tracedecay 9.9.9", result.stderr)
+ self.assertFalse(self.log.exists())
+
+ def test_missing_explicit_binary_names_the_build_command(self):
+ result = self.run_script(TRACEDECAY_BIN=str(self.built.with_name("absent")))
+ self.assertEqual(result.returncode, 2)
+ self.assertEqual(result.stdout, "")
+ self.assertIn("bazel build --config=release //crates/tracedecay-cli:tracedecay", result.stderr)
+
+
+if __name__ == "__main__":
+ unittest.main()
From e253929260b7542538aa81fd17ae15fe4a0b35a1 Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 16:34:07 +0000
Subject: [PATCH 18/89] build(dist): apply Bazel asupersync patches to the
extracted-crate build
---
BUILD.bazel | 1 +
scripts/BUILD.bazel | 11 +++++
scripts/check-distribution-acceptance.sh | 51 +++++++++++++++++++++++-
scripts/test-bazel-dependency-patches.py | 48 ++++++++++++++++++++++
4 files changed, 110 insertions(+), 1 deletion(-)
create mode 100644 scripts/test-bazel-dependency-patches.py
diff --git a/BUILD.bazel b/BUILD.bazel
index 0e6f37c990..54139007a8 100644
--- a/BUILD.bazel
+++ b/BUILD.bazel
@@ -12,6 +12,7 @@ npm_link_all_packages(name = "node_modules")
exports_files(
[
".github/release-targets.json",
+ "MODULE.bazel",
"version.txt",
"Cargo.toml",
"clippy.toml",
diff --git a/scripts/BUILD.bazel b/scripts/BUILD.bazel
index 67aaa9ed2e..c55e526b23 100644
--- a/scripts/BUILD.bazel
+++ b/scripts/BUILD.bazel
@@ -51,6 +51,17 @@ py_test(
main = "test-build-eval-binary.py",
)
+py_test(
+ name = "bazel_dependency_patches_test",
+ size = "small",
+ srcs = ["test-bazel-dependency-patches.py"],
+ data = [
+ "check-distribution-acceptance.sh",
+ "//:MODULE.bazel",
+ ] + glob(["bazel/patches/*.patch"]),
+ main = "test-bazel-dependency-patches.py",
+)
+
py_test(
name = "ambient_tokio_check_test",
size = "small",
diff --git a/scripts/check-distribution-acceptance.sh b/scripts/check-distribution-acceptance.sh
index 9a2b686071..c85f68f40e 100755
--- a/scripts/check-distribution-acceptance.sh
+++ b/scripts/check-distribution-acceptance.sh
@@ -592,8 +592,46 @@ assert_required_assets "$root_package" "$cli_package"
[[ ! -e "$package_root/.git" && ! -L "$package_root/.git" ]] ||
die "extracted package workspace unexpectedly contains .git metadata: $package_root/.git"
+# The Bazel release build compiles asupersync with the patches listed in
+# MODULE.bazel, which live in scripts/bazel/patches/asupersync-*.patch. Cargo
+# has no patch-file mechanism, so the extracted-crate build compiles a copy of
+# the same pinned source with those same files applied, in the sorted order
+# crate_universe applies them. scripts/test-bazel-dependency-patches.py keeps
+# the file set and the MODULE.bazel annotation identical.
+asupersync_source=$(
+ cargo metadata \
+ --manifest-path "$staged/Cargo.toml" \
+ --format-version 1 |
+ python3 -c '
+import json
+import os
+import sys
+
+manifests = [
+ package["manifest_path"]
+ for package in json.load(sys.stdin)["packages"]
+ if package["name"] == "asupersync"
+]
+if len(manifests) != 1:
+ sys.exit(f"expected exactly one asupersync package, found {len(manifests)}")
+print(os.path.dirname(manifests[0]))
+'
+) || die "could not locate the asupersync source"
+patched_asupersync="$work/patched-deps/asupersync"
+mkdir -p -- "$patched_asupersync"
+tar -C "$asupersync_source" --exclude=.git -cf - . | tar -C "$patched_asupersync" -xf -
+shopt -s nullglob
+asupersync_patches=("$repo"/scripts/bazel/patches/asupersync-*.patch)
+shopt -u nullglob
+[[ ${#asupersync_patches[@]} -gt 0 ]] ||
+ die "no asupersync patches found under scripts/bazel/patches"
+for asupersync_patch in "${asupersync_patches[@]}"; do
+ (cd -- "$patched_asupersync" && git apply -p1 "$asupersync_patch") ||
+ die "could not apply $asupersync_patch to asupersync"
+done
+
patch_config="$work/packaged-crates.toml"
-python3 - "$metadata" "$packages" "$staged/Cargo.toml" >"$patch_config" <<'PY'
+python3 - "$metadata" "$packages" "$staged/Cargo.toml" "$patched_asupersync" >"$patch_config" <<'PY'
import json
import pathlib
import sys
@@ -634,6 +672,17 @@ for name, spec in workspace_manifest.get("patch", {}).get("crates-io", {}).items
f"distribution acceptance: workspace path patch {name} is not staged at {patched}"
)
print(f'{json.dumps(name)} = {{ path = {json.dumps(str(patched))} }}')
+# The asupersync copy patched above replaces the pinned git source, so the
+# runtime the extracted build links is the one Bazel builds.
+asupersync_spec = workspace_manifest["workspace"]["dependencies"]["asupersync"]
+if "git" not in asupersync_spec:
+ raise SystemExit(
+ "distribution acceptance: asupersync is no longer a git dependency; "
+ "patch it through [patch.crates-io] here or drop its Bazel patches"
+ )
+print()
+print(f'[patch.{json.dumps(asupersync_spec["git"])}]')
+print(f'asupersync = {{ path = {json.dumps(sys.argv[4])} }}')
# Extracted packages do not see workspace Cargo.toml profiles. Copy
# [profile.dist] from that manifest so `--profile dist` matches shipping.
dist_profile = workspace_manifest.get("profile", {}).get("dist")
diff --git a/scripts/test-bazel-dependency-patches.py b/scripts/test-bazel-dependency-patches.py
new file mode 100644
index 0000000000..459c2c8431
--- /dev/null
+++ b/scripts/test-bazel-dependency-patches.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+"""The Bazel dependency patches and the extracted-crate Cargo build stay in step.
+
+check-distribution-acceptance.sh applies every scripts/bazel/patches/asupersync-*.patch
+to the asupersync source it compiles. MODULE.bazel lists the patches Bazel applies.
+A patch present in only one of them means the two builds run different runtimes.
+"""
+
+import re
+import unittest
+from pathlib import Path
+
+ROOT = Path(__file__).absolute().parent.parent
+PATCHES = ROOT / "scripts" / "bazel" / "patches"
+LABEL = re.compile(r'"//scripts:bazel/patches/([^"]+\.patch)"')
+
+
+def annotation_patches(module: str, crate: str) -> list[str]:
+ for block in re.findall(r"crate\.annotation\((.*?)\n\)", module, re.DOTALL):
+ if f'crate = "{crate}"' in block:
+ return LABEL.findall(block)
+ raise AssertionError(f"MODULE.bazel has no crate.annotation for {crate}")
+
+
+class DependencyPatchParityTest(unittest.TestCase):
+ def setUp(self):
+ self.module = (ROOT / "MODULE.bazel").read_text()
+
+ def test_asupersync_patches_match_the_distribution_build(self):
+ applied_by_distribution = sorted(p.name for p in PATCHES.glob("asupersync-*.patch"))
+ self.assertTrue(applied_by_distribution)
+ # crate_universe applies an annotation's patches in sorted label order.
+ self.assertEqual(
+ annotation_patches(self.module, "asupersync"), applied_by_distribution
+ )
+
+ def test_distribution_build_applies_that_glob(self):
+ script = (ROOT / "scripts" / "check-distribution-acceptance.sh").read_text()
+ self.assertIn("scripts/bazel/patches/asupersync-*.patch", script)
+
+ def test_no_patch_file_is_unreferenced(self):
+ referenced = set(LABEL.findall(self.module))
+ unreferenced = {p.name for p in PATCHES.glob("*.patch")} - referenced
+ self.assertEqual(unreferenced, set())
+
+
+if __name__ == "__main__":
+ unittest.main()
From 0ec2ce53693e25560e3fa9306409296c28f23303 Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 16:34:53 +0000
Subject: [PATCH 19/89] ci: mark the Windows cargo check as a type-check-only
exception
Also runs the eval binary script test in the hermetic eval workflow.
---
.github/workflows/ci.yml | 17 +++++++++++------
.github/workflows/hermetic-evals.yml | 4 ++++
2 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 6278fb284b..17d047cdf0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -591,12 +591,17 @@ jobs:
- name: Run blocking Clippy policy
run: bazel build --config=ci --config=clippy //...
- # Master pushes run no Windows lane, so `cfg(windows)` code otherwise
- # first compiles in the release build. Bazel has no MSVC toolchain on
- # Linux, so this cross-target type check stays on Cargo: `cargo check`
- # needs no linker, and mingw supplies the C compiler the native build
- # scripts run. macOS has no equivalent: its build scripts need the Apple
- # SDK headers.
+ # The one deliberate Cargo step left in CI, and only because Bazel cannot
+ # replace it yet. Master pushes and ordinary PRs run no Windows lane
+ # (`windows-test` is dispatch-only), so `cfg(windows)` code otherwise
+ # first compiles in the release build. The workspace registers no
+ # Windows target toolchain for Linux, so this cross-target type check
+ # stays on Cargo: `cargo check` needs no linker, and mingw supplies the
+ # C compiler the native build scripts run. It type-checks only and
+ # produces no executable, so no eval, test, or release consumes its
+ # output and it cannot diverge a shipped runtime. Remove it when Bazel
+ # grows a Linux-hosted Windows target. macOS has no equivalent: its
+ # build scripts need the Apple SDK headers.
- name: Install pinned toolchain and Windows check target
run: |
rustup toolchain install
diff --git a/.github/workflows/hermetic-evals.yml b/.github/workflows/hermetic-evals.yml
index 75d634062d..17a95296d6 100644
--- a/.github/workflows/hermetic-evals.yml
+++ b/.github/workflows/hermetic-evals.yml
@@ -5,12 +5,14 @@ on:
paths:
- "evals/hermetic/**"
- "evals/agent_adoption/**"
+ - "scripts/build-eval-binary.sh"
- ".github/workflows/hermetic-evals.yml"
push:
branches: [master]
paths:
- "evals/hermetic/**"
- "evals/agent_adoption/**"
+ - "scripts/build-eval-binary.sh"
- ".github/workflows/hermetic-evals.yml"
permissions:
@@ -34,6 +36,7 @@ jobs:
run: |
bash -n evals/hermetic/run.sh
bash -n evals/agent_adoption/run.sh
+ bash -n scripts/build-eval-binary.sh
- name: Run scorer and shell-runner regressions
run: >-
bazel test
@@ -41,4 +44,5 @@ jobs:
//evals/hermetic:run_test
//evals/hermetic:scorecard_test
//evals/agent_adoption:selftest
+ //scripts:build_eval_binary_test
--test_output=errors
From d1539a103dfcad1d85abc41c3e8348388a35b858 Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 17:48:54 +0000
Subject: [PATCH 20/89] fix(projection): settle passes parked on terminal
session collisions
---
.../src/observation_projection/rebuild.rs | 7 +-
.../src/session_project_rebind_tests.rs | 9 ++
.../tests/transcript_ingest_suite/codex.rs | 111 ++++++++++++++++++
.../restart_atomicity.rs | 22 +++-
4 files changed, 143 insertions(+), 6 deletions(-)
diff --git a/crates/tracedecay-global-db/src/observation_projection/rebuild.rs b/crates/tracedecay-global-db/src/observation_projection/rebuild.rs
index 6332fdf643..1234a622ea 100644
--- a/crates/tracedecay-global-db/src/observation_projection/rebuild.rs
+++ b/crates/tracedecay-global-db/src/observation_projection/rebuild.rs
@@ -298,9 +298,14 @@ async fn next_ready_projection_head(
))
}
+/// Whether a later drain still has work. A row parked on a terminal collision
+/// is never drained again, so it must not keep every pass reporting more work.
async fn projection_queue_has_items(conn: &impl QueryExecutor) -> ProjectionStoreResult {
let mut rows = conn
- .query("SELECT 1 FROM projection_queue LIMIT 1", ())
+ .query(
+ "SELECT 1 FROM projection_queue WHERE next_retry_at_micros < ?1 LIMIT 1",
+ params![PROJECTION_TERMINAL_RETRY_MICROS],
+ )
.await
.map_err(|error| storage("probe projection queue", error))?;
Ok(rows
diff --git a/crates/tracedecay-global-db/src/session_project_rebind_tests.rs b/crates/tracedecay-global-db/src/session_project_rebind_tests.rs
index 5f0b994967..6ee48632b2 100644
--- a/crates/tracedecay-global-db/src/session_project_rebind_tests.rs
+++ b/crates/tracedecay-global-db/src/session_project_rebind_tests.rs
@@ -652,6 +652,15 @@ async fn genuine_session_collision_is_recorded_and_later_sessions_project() {
store.next_queued_observation().await.unwrap().is_none(),
"the terminal collision must not stay at the head of the queue"
);
+ let window = store
+ .project_queued_observations(8)
+ .await
+ .unwrap()
+ .expect("the store drains in windows");
+ assert!(
+ window.items.is_empty() && !window.has_more,
+ "a parked terminal collision is not pending work, or every pass reports backpressure"
+ );
}
fn session_fact_with_transcript(cwd: &str, transcript_path: &str) -> CanonicalObservationFactV1 {
diff --git a/crates/tracedecay/tests/transcript_ingest_suite/codex.rs b/crates/tracedecay/tests/transcript_ingest_suite/codex.rs
index 6f709b53c6..51fa735ec6 100644
--- a/crates/tracedecay/tests/transcript_ingest_suite/codex.rs
+++ b/crates/tracedecay/tests/transcript_ingest_suite/codex.rs
@@ -4,9 +4,11 @@ use tempfile::TempDir;
use tracedecay_project::test_support::host_admission::HostAdmissionTestRuntimeV1;
use tracedecay_sessions::admission::HostAdmissionScope;
use tracedecay_sessions::runtime::SessionProvider;
+use tracedecay_sessions::runtime::shared::durable_project_path_key;
use crate::restart_atomicity::{
ingest_global_sources_for_provider, ingest_user_provider, open_project_session_db,
+ run_user_provider_pass,
};
pub(crate) fn write_jsonl(path: &std::path::Path, lines: &[serde_json::Value]) {
@@ -497,3 +499,112 @@ pub(crate) fn write_codex_rollout_with_structured_events(
);
path
}
+
+/// Seeds a `moved-session` row under another working directory, naming
+/// `stored_transcript` (or the rollout itself), then runs user-scope passes
+/// over the session's rollout until one reports no failure. Returns the
+/// runtime and the number of passes that took.
+async fn ingest_rollout_over_stored_session(
+ tmp: &TempDir,
+ stored_transcript: Option<&str>,
+) -> (HostAdmissionTestRuntimeV1, usize) {
+ let home = tmp.path().join("home");
+ let general = tmp.path().join("general-chat");
+ let stored_cwd = tmp.path().join("earlier-cwd");
+ std::fs::create_dir_all(&general).unwrap();
+ std::fs::create_dir_all(&stored_cwd).unwrap();
+ let rollout = write_codex_rollout(&home, &general, "moved-session");
+ let runtime = HostAdmissionTestRuntimeV1::profile(tmp.path().join("profile"))
+ .await
+ .unwrap();
+ let stored = tracedecay_sessions::runtime::SessionRecord {
+ provider: "codex".to_owned(),
+ session_id: "moved-session".to_owned(),
+ project_key: "user".to_owned(),
+ project_path: durable_project_path_key(&stored_cwd.to_string_lossy()),
+ title: None,
+ started_at: Some(1),
+ ended_at: None,
+ transcript_path: Some(
+ stored_transcript.map_or_else(|| rollout.to_string_lossy().into_owned(), str::to_owned),
+ ),
+ metadata_json: None,
+ parent_session_id: None,
+ is_subagent: false,
+ agent_id: None,
+ parent_tool_use_id: None,
+ };
+ assert!(
+ runtime
+ .upsert_session_for_test(HostAdmissionScope::Profile, &stored)
+ .await
+ .unwrap()
+ );
+
+ let mut passes = 0;
+ loop {
+ passes += 1;
+ let outcome =
+ run_user_provider_pass(&runtime, &home, SessionProvider::Codex, Vec::new()).await;
+ if outcome.failures.is_empty() {
+ break;
+ }
+ assert!(passes < 4, "still retrying: {:?}", outcome.failures);
+ }
+ // Settled: another pass has nothing to retry.
+ ingest_user_provider(&runtime, &home, SessionProvider::Codex, Vec::new()).await;
+ (runtime, passes)
+}
+
+async fn billing_messages(runtime: &HostAdmissionTestRuntimeV1) -> usize {
+ runtime
+ .search_session_messages_for_test(
+ HostAdmissionScope::Profile,
+ "codex",
+ None,
+ "billing pipeline",
+ 10,
+ )
+ .await
+ .unwrap()
+ .len()
+}
+
+/// A session row written earlier under another working directory (a hook
+/// payload, LCM, or an older build) still names the same transcript. The
+/// rollout's messages must project and the stored directory stays. Before the
+/// fix each message was parked as a `project_path` session collision and every
+/// pass reported `ingest_pass_backpressured` again.
+#[tokio::test]
+async fn user_scope_projects_a_codex_rollout_whose_cwd_differs_from_the_stored_session() {
+ let tmp = TempDir::new().unwrap();
+ let (runtime, passes) = ingest_rollout_over_stored_session(&tmp, None).await;
+
+ assert_eq!(passes, 1, "one pass must settle, not retry");
+ assert_eq!(billing_messages(&runtime).await, 2, "both messages project");
+ let session = runtime
+ .session_for_test(HostAdmissionScope::Profile, "codex", "moved-session")
+ .await
+ .unwrap()
+ .unwrap();
+ assert_eq!(
+ session.project_path,
+ durable_project_path_key(&tmp.path().join("earlier-cwd").to_string_lossy())
+ );
+}
+
+/// A stored session that names a different transcript is a genuine duplicate:
+/// its messages are still refused as a collision. The parked collision must
+/// not keep every later pass reporting `ingest_pass_backpressured`.
+#[tokio::test]
+async fn user_scope_settles_after_a_genuine_codex_session_collision() {
+ let tmp = TempDir::new().unwrap();
+ let (runtime, _) =
+ ingest_rollout_over_stored_session(&tmp, Some("/elsewhere/other-rollout.jsonl")).await;
+
+ assert_eq!(
+ billing_messages(&runtime).await,
+ 0,
+ "a conflicting transcript identity stays a collision"
+ );
+}
diff --git a/crates/tracedecay/tests/transcript_ingest_suite/restart_atomicity.rs b/crates/tracedecay/tests/transcript_ingest_suite/restart_atomicity.rs
index e305f80af4..a30ab18bd8 100644
--- a/crates/tracedecay/tests/transcript_ingest_suite/restart_atomicity.rs
+++ b/crates/tracedecay/tests/transcript_ingest_suite/restart_atomicity.rs
@@ -217,6 +217,23 @@ pub(super) async fn ingest_user_provider(
home: &Path,
provider: SessionProvider,
registered_roots: Vec,
+) -> IngestPassOutcome {
+ let outcome = run_user_provider_pass(runtime, home, provider, registered_roots).await;
+ assert!(
+ outcome.failures.is_empty(),
+ "user {provider:?} pass failed: {:?}",
+ outcome.failures
+ );
+ outcome
+}
+
+/// One production user-scope pass, whatever it reports. A bounded pass may
+/// report backpressure while discovery still has ground to cover.
+pub(super) async fn run_user_provider_pass(
+ runtime: &HostAdmissionTestRuntimeV1,
+ home: &Path,
+ provider: SessionProvider,
+ registered_roots: Vec,
) -> IngestPassOutcome {
let database = runtime
.registered_database(HostAdmissionScope::Profile)
@@ -236,11 +253,6 @@ pub(super) async fn ingest_user_provider(
),
)
.await;
- assert!(
- outcome.failures.is_empty(),
- "user {provider:?} pass failed: {:?}",
- outcome.failures
- );
outcome
}
From 5dc09fa586d02ba7653e00dd297d40d837cc73bf Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 18:02:41 +0000
Subject: [PATCH 21/89] test(session-runtime): cut refresh groups without a
wall-clock deadline
---
.../worker.rs | 39 ++++++++++++++-----
1 file changed, 30 insertions(+), 9 deletions(-)
diff --git a/crates/tracedecay-session-runtime/src/session_temporal_refresh_scheduler/worker.rs b/crates/tracedecay-session-runtime/src/session_temporal_refresh_scheduler/worker.rs
index 489de73c34..5427dd0fa8 100644
--- a/crates/tracedecay-session-runtime/src/session_temporal_refresh_scheduler/worker.rs
+++ b/crates/tracedecay-session-runtime/src/session_temporal_refresh_scheduler/worker.rs
@@ -1915,13 +1915,12 @@ mod tests {
use tracedecay_runtime_core::db::engine::params;
use tracedecay_sessions::runtime::{SessionMessageRecord, SessionRecord};
- /// Wide enough that one item's commit lands inside it on a loaded host.
- const COMMIT_DEADLINE: Duration = Duration::from_secs(2);
-
- /// Commits `item_count` items. A group of several items never commits
- /// before the deadline, and neither does a `slow` item alone; every other
- /// item alone commits at once. Returns how many commits the deadline cut
- /// short.
+ /// Commits `item_count` items. A group of several items never commits, and
+ /// neither does a `slow` item alone: its transaction ends in the
+ /// `DeadlineExceeded` that storage reports when its own deadline passes.
+ /// Every other item alone commits. Real writes race no wall-clock deadline
+ /// (`Duration::MAX` starts no timer), so host load cannot cut an item that
+ /// the test means to commit. Returns how many commits were cut short.
async fn timed_refresh_group_progress(item_count: usize, slow: &[usize]) -> usize {
let harness = RegisteredGlobalDbHarness::open("refresh-group-progress").await;
let database = &harness.registered;
@@ -1937,7 +1936,7 @@ mod tests {
let cut_short = std::sync::atomic::AtomicUsize::new(0);
let cut_short = &cut_short;
- let outcomes = commit_refresh_groups(item_count, COMMIT_DEADLINE, |range| async move {
+ let outcomes = commit_refresh_groups(item_count, Duration::MAX, |range| async move {
let transaction = database
.begin_write_transaction()
.await
@@ -1953,7 +1952,7 @@ mod tests {
}
if range.len() > 1 || slow.contains(&range.start) {
cut_short.fetch_add(1, Ordering::SeqCst);
- std::future::pending::<()>().await;
+ return Err(SessionStoreError::DeadlineExceeded);
}
transaction
.commit()
@@ -2010,6 +2009,28 @@ mod tests {
);
}
+ /// The deadline itself ends a group that never commits. No I/O races it:
+ /// a commit that is ready at its first poll wins even past the deadline,
+ /// so only the pending group is ever cut, however loaded the host.
+ #[tokio::test]
+ async fn a_refresh_group_that_outlives_its_deadline_is_retried_item_by_item() {
+ let outcomes = commit_refresh_groups(3, Duration::from_millis(10), |range| async move {
+ if range.len() > 1 {
+ std::future::pending::<()>().await;
+ }
+ Ok(range.map(Ok).collect())
+ })
+ .await
+ .unwrap();
+ assert_eq!(
+ outcomes
+ .into_iter()
+ .map(|outcome| outcome.unwrap())
+ .collect::>(),
+ [0, 1, 2]
+ );
+ }
+
#[tokio::test]
async fn a_timed_out_completion_stays_claimable_by_the_next_pass() {
let harness = RegisteredGlobalDbHarness::open("refresh-timed-out-completion").await;
From 8fdd7f9aa9eff6bf3033ff662eadc312516325d8 Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 18:10:47 +0000
Subject: [PATCH 22/89] test(sessions): return the user pass outcome directly
---
.../tests/transcript_ingest_suite/restart_atomicity.rs | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/crates/tracedecay/tests/transcript_ingest_suite/restart_atomicity.rs b/crates/tracedecay/tests/transcript_ingest_suite/restart_atomicity.rs
index a30ab18bd8..7714cf7af4 100644
--- a/crates/tracedecay/tests/transcript_ingest_suite/restart_atomicity.rs
+++ b/crates/tracedecay/tests/transcript_ingest_suite/restart_atomicity.rs
@@ -241,7 +241,7 @@ pub(super) async fn run_user_provider_pass(
let authority =
GlobalDbSessionIngestAuthority::new(database).with_background_cpu(runtime.background_cpu());
let shard = &database.binding().shard_id;
- let outcome = with_transcript_source_profile(
+ with_transcript_source_profile(
ProfileRoot::new(runtime.profile_root_for_test()).with_home(home),
ingest_user_global_sources_for_provider_with_roots_bounded(
(&shard.brain_id, &shard.profile_id, &authority),
@@ -252,8 +252,7 @@ pub(super) async fn run_user_provider_pass(
&ObservationCancellation::default(),
),
)
- .await;
- outcome
+ .await
}
/// Runs one Claude source through the production observation pipeline against
From 73e6ccfd20d682b82ad4d49752bddccb3490a08b Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 19:33:29 +0000
Subject: [PATCH 23/89] fix(cli): read glibc counters through mallinfo on the
2.31 baseline
---
crates/tracedecay-cli/tests/c_library_allocator.rs | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/crates/tracedecay-cli/tests/c_library_allocator.rs b/crates/tracedecay-cli/tests/c_library_allocator.rs
index c434a4fd49..df4f9ef0ad 100644
--- a/crates/tracedecay-cli/tests/c_library_allocator.rs
+++ b/crates/tracedecay-cli/tests/c_library_allocator.rs
@@ -19,10 +19,15 @@ static MIMALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
const MIB: usize = 1024 * 1024;
/// Bytes glibc's arenas and its mmapped chunks hold for live allocations.
+///
+/// The hermetic toolchain links against glibc 2.31, which predates
+/// `mallinfo2`. `mallinfo` reports the same counters as `int`, which this
+/// test's few-MiB working set stays far below.
fn glibc_in_use() -> usize {
- // SAFETY: `mallinfo2` only reads allocator statistics.
- let info = unsafe { libc::mallinfo2() };
- info.uordblks + info.hblkhd
+ // SAFETY: `mallinfo` only reads allocator statistics.
+ let info = unsafe { libc::mallinfo() };
+ usize::try_from(info.uordblks).expect("in-use arena bytes fit in int")
+ + usize::try_from(info.hblkhd).expect("mmapped bytes fit in int")
}
#[test]
From ee650a8c6b1955731d26beb8e7e8d8449694d5a9 Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 20:17:10 +0000
Subject: [PATCH 24/89] fix(ci): expose packaged LSP smoke imports to Bazel
---
scripts/BUILD.bazel | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/scripts/BUILD.bazel b/scripts/BUILD.bazel
index c5ff54479a..11df536bae 100644
--- a/scripts/BUILD.bazel
+++ b/scripts/BUILD.bazel
@@ -61,9 +61,24 @@ py_binary(
"lib/portable_process.py",
"lib/process_control.py",
],
+ imports = ["."],
main = "check-packaged-lsp-bridge.py",
)
+py_test(
+ name = "packaged_lsp_bridge_test",
+ size = "small",
+ srcs = [
+ "check-packaged-lsp-bridge.py",
+ "test-check-packaged-lsp-bridge.py",
+ "lib/__init__.py",
+ "lib/portable_process.py",
+ "lib/process_control.py",
+ ],
+ imports = ["."],
+ main = "test-check-packaged-lsp-bridge.py",
+)
+
exports_files(
[
"bazel/rust_test.bzl",
From 9fdec8ede126ef250d048463dcf33b3a76743788 Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 20:18:08 +0000
Subject: [PATCH 25/89] test(query): write historical fixtures without runfile
permissions
---
.../tests/search_quality_suite/candidate_producers.rs | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/crates/tracedecay-query/tests/search_quality_suite/candidate_producers.rs b/crates/tracedecay-query/tests/search_quality_suite/candidate_producers.rs
index 1535b0fc07..8542d1c80e 100644
--- a/crates/tracedecay-query/tests/search_quality_suite/candidate_producers.rs
+++ b/crates/tracedecay-query/tests/search_quality_suite/candidate_producers.rs
@@ -2685,12 +2685,13 @@ fn reader_refuses_historical_v10_writer_artifact_as_incompatible() {
let directory = tempfile::tempdir().expect("private v10 reopen dir");
let artifact_path = directory.path().join("lexical-artifact-v10.sqlite");
- std::fs::copy(checked_in, &artifact_path).expect("copy historical v10 fixture");
+ let bytes = std::fs::read(checked_in).expect("read historical v10 fixture");
+ // Bazel runfiles are read-only; create an owned file without copying their mode.
+ std::fs::write(&artifact_path, &bytes).expect("write historical v10 fixture");
drop(
tracedecay_private_fs::make_private_file(&artifact_path)
.expect("restore private-file protection for content-addressed open"),
);
- let bytes = std::fs::read(&artifact_path).expect("read historical v10 fixture");
let file_size_bytes = u64::try_from(bytes.len()).expect("v10 fixture length");
let digest = ManifestDigest::new(format!("sha256:{}", hex::encode(Sha256::digest(&bytes))))
.expect("v10 fixture digest");
From 64e8b5e8c2c16be5dfad0ac16fd620e5f9f0d800 Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 20:18:11 +0000
Subject: [PATCH 26/89] fix(evals): honor consent and platform executable paths
---
benchmark_data/BUILD.bazel | 2 ++
evals/hermetic/BUILD.bazel | 2 ++
evals/memory/BUILD.bazel | 15 +++++++++++++++
evals/memory/run_real_model.py | 6 ++++--
evals/memory/test_run_real_model.py | 26 ++++++++++++++++++++++++++
scripts/BUILD.bazel | 1 +
scripts/build-eval-binary.sh | 6 +++++-
scripts/test-build-eval-binary.py | 26 +++++++++++++++++++++++++-
8 files changed, 80 insertions(+), 4 deletions(-)
diff --git a/benchmark_data/BUILD.bazel b/benchmark_data/BUILD.bazel
index c510e6d63a..f2c7affbe9 100644
--- a/benchmark_data/BUILD.bazel
+++ b/benchmark_data/BUILD.bazel
@@ -82,3 +82,5 @@ exports_files(["session-temporal/workload-v1.json"],
visibility = ["//visibility:public"],
)
# END generated by scripts/bazel/gen_builds.py
+
+exports_files(["run_benchmarks.py"], visibility = ["//evals/memory:__pkg__"])
diff --git a/evals/hermetic/BUILD.bazel b/evals/hermetic/BUILD.bazel
index b16f0187d2..8761819a7d 100644
--- a/evals/hermetic/BUILD.bazel
+++ b/evals/hermetic/BUILD.bazel
@@ -28,3 +28,5 @@ py_test(
imports = ["."],
legacy_create_init = 0,
)
+
+exports_files(["score.py"], visibility = ["//evals/memory:__pkg__"])
diff --git a/evals/memory/BUILD.bazel b/evals/memory/BUILD.bazel
index af098d6a49..2b78708c10 100644
--- a/evals/memory/BUILD.bazel
+++ b/evals/memory/BUILD.bazel
@@ -1,3 +1,18 @@
+load("@rules_python//python:defs.bzl", "py_test")
+
+py_test(
+ name = "real_model_runner_test",
+ srcs = ["run_real_model.py", "test_run_real_model.py"],
+ data = [
+ "//benchmark_data:run_benchmarks.py",
+ "//evals/hermetic:score.py",
+ "//scripts:build-eval-binary.sh",
+ ],
+ imports = ["."],
+ legacy_create_init = 0,
+ main = "test_run_real_model.py",
+)
+
filegroup(
name = "scenarios",
srcs = glob(["scenarios/*.json"]),
diff --git a/evals/memory/run_real_model.py b/evals/memory/run_real_model.py
index f966448a81..0136f31486 100755
--- a/evals/memory/run_real_model.py
+++ b/evals/memory/run_real_model.py
@@ -699,7 +699,6 @@ def evaluate_assertions(scenario, tracedecay_bin, fixture, env):
def main(argv):
args = parse_args(argv)
- args.tracedecay_bin = resolve_tracedecay_bin(args.tracedecay_bin)
scenarios = load_scenarios(args.scenario)
if not scenarios:
sys.exit("no runnable scenarios selected")
@@ -712,7 +711,7 @@ def main(argv):
"timestamp": timestamp,
"driver": args.driver,
"model": args.model,
- "tracedecay_bin": str(args.tracedecay_bin),
+ "tracedecay_bin": None,
"scenarios": [],
}
@@ -729,6 +728,9 @@ def main(argv):
print(f"\nblocked report written to {report_path}", file=sys.stderr)
return 2
+ args.tracedecay_bin = resolve_tracedecay_bin(args.tracedecay_bin)
+ report["tracedecay_bin"] = args.tracedecay_bin
+
overall_ok = True
for scenario in scenarios:
eval_env = create_eval_environment(scenario["id"])
diff --git a/evals/memory/test_run_real_model.py b/evals/memory/test_run_real_model.py
index a171e039b4..179e2383fb 100644
--- a/evals/memory/test_run_real_model.py
+++ b/evals/memory/test_run_real_model.py
@@ -26,6 +26,14 @@
SCORE_SPEC.loader.exec_module(hermetic_score)
+def setUpModule():
+ runs = tempfile.TemporaryDirectory()
+ unittest.addModuleCleanup(runs.cleanup)
+ patch = mock.patch.object(run_real_model, "RUNS_DIR", Path(runs.name))
+ patch.start()
+ unittest.addModuleCleanup(patch.stop)
+
+
class HermesHomeSelectionTest(unittest.TestCase):
def test_driver_defaults_resolve_before_launch_and_reporting(self):
self.assertEqual(run_real_model.parse_args([]).model, "gpt-5.4-mini")
@@ -72,6 +80,24 @@ def test_install_uses_the_isolated_user_hermes_home(self):
class BinaryResolutionTest(unittest.TestCase):
+ def test_blocked_run_writes_report_without_resolving_a_binary(self):
+ for flags in ([], ["--agent-turn"], ["--i-understand-model-cost"]):
+ with self.subTest(flags=flags), tempfile.TemporaryDirectory() as tmp:
+ with (
+ mock.patch.object(run_real_model, "RUNS_DIR", Path(tmp)),
+ mock.patch.object(run_real_model, "resolve_tracedecay_bin") as resolve,
+ mock.patch.object(run_real_model, "load_scenarios", return_value=[{"id": "test"}]),
+ contextlib.redirect_stdout(io.StringIO()),
+ contextlib.redirect_stderr(io.StringIO()),
+ ):
+ self.assertEqual(run_real_model.main(flags), 2)
+ resolve.assert_not_called()
+ reports = list(Path(tmp).glob("*/report.json"))
+ self.assertEqual(len(reports), 1)
+ report = json.loads(reports[0].read_text())
+ self.assertEqual(report["status"], "blocked")
+ self.assertIsNone(report["tracedecay_bin"])
+
def test_explicit_binary_is_used_and_must_exist(self):
with tempfile.TemporaryDirectory() as tmp:
binary = Path(tmp) / "tracedecay"
diff --git a/scripts/BUILD.bazel b/scripts/BUILD.bazel
index 85dd02f7d6..5048ad182f 100644
--- a/scripts/BUILD.bazel
+++ b/scripts/BUILD.bazel
@@ -85,6 +85,7 @@ py_binary(
exports_files(
[
"bazel/rust_test.bzl",
+ "build-eval-binary.sh",
"test-rust-cache-lineage.py",
"test-check-packaged-lsp-bridge.py",
"test-check-sdk-publish-workflow.py",
diff --git a/scripts/build-eval-binary.sh b/scripts/build-eval-binary.sh
index cdca2ebbda..2b2e82a804 100755
--- a/scripts/build-eval-binary.sh
+++ b/scripts/build-eval-binary.sh
@@ -42,7 +42,11 @@ else
fail "eval binary build failed; not reusing any older binary. Reproduce with: $build_cmd"
bazel_bin="$(cd "$repo_root" && bazel info --config="$config" bazel-bin)" ||
fail "could not resolve the Bazel output directory (bazel info --config=$config bazel-bin)"
- bin="$bazel_bin/crates/tracedecay-cli/tracedecay"
+ exe=""
+ case "$(uname -s)" in
+ MINGW*|MSYS*|CYGWIN*) exe=".exe" ;;
+ esac
+ bin="$bazel_bin/crates/tracedecay-cli/tracedecay${exe}"
[[ -f "$bin" && -x "$bin" ]] ||
fail "expected a binary at $bin after: $build_cmd"
origin="bazel --config=$config $target"
diff --git a/scripts/test-build-eval-binary.py b/scripts/test-build-eval-binary.py
index 22de025f37..9f6751f422 100644
--- a/scripts/test-build-eval-binary.py
+++ b/scripts/test-build-eval-binary.py
@@ -37,7 +37,8 @@ def setUp(self):
self.built.write_text("#!/bin/sh\necho tracedecay 1.2.3\n")
self.built.chmod(0o755)
self.log = root / "bazel.log"
- fakes = root / "fakes"
+ self.fakes = root / "fakes"
+ fakes = self.fakes
fakes.mkdir()
bazel = fakes / "bazel"
bazel.write_text(FAKE_BAZEL)
@@ -68,6 +69,16 @@ def test_builds_the_shipping_target_and_prints_its_identity(self):
self.assertIn("tracedecay 1.2.3", result.stderr)
self.assertIn("sha256 ", result.stderr)
+ def test_windows_build_selects_the_executable_suffix(self):
+ windows_binary = self.built.with_suffix(".exe")
+ self.built.rename(windows_binary)
+ uname = self.fakes / "uname"
+ uname.write_text("#!/bin/sh\necho MINGW64_NT-10.0\n")
+ uname.chmod(0o755)
+ result = self.run_script()
+ self.assertEqual(result.returncode, 0, result.stderr)
+ self.assertEqual(result.stdout.strip(), str(windows_binary))
+
def test_config_override_reaches_bazel(self):
result = self.run_script(TRACEDECAY_EVAL_BAZEL_CONFIG="dist")
self.assertEqual(result.returncode, 0, result.stderr)
@@ -89,6 +100,19 @@ def test_explicit_binary_skips_the_build(self):
self.assertIn("tracedecay 9.9.9", result.stderr)
self.assertFalse(self.log.exists())
+ def test_isolated_reentry_reuses_the_selected_binary_without_another_build(self):
+ outer = self.run_script()
+ self.assertEqual(outer.returncode, 0, outer.stderr)
+ with tempfile.TemporaryDirectory() as isolated_home:
+ child = self.run_script(
+ TRACEDECAY_BIN=outer.stdout.strip(),
+ HOME=isolated_home,
+ XDG_CACHE_HOME=str(Path(isolated_home) / ".cache"),
+ )
+ self.assertEqual(child.returncode, 0, child.stderr)
+ self.assertEqual(child.stdout, outer.stdout)
+ self.assertEqual(sum(line.startswith("build ") for line in self.log.read_text().splitlines()), 1)
+
def test_missing_explicit_binary_names_the_build_command(self):
result = self.run_script(TRACEDECAY_BIN=str(self.built.with_name("absent")))
self.assertEqual(result.returncode, 2)
From 451721797b8ae53bc539072fc19994df9cb66d34 Mon Sep 17 00:00:00 2001
From: ScriptedAlchemy
Date: Sun, 18 Oct 2026 20:27:12 +0000
Subject: [PATCH 27/89] fix(dashboard): decode canonical settings settlements
---
.../workflows/dashboard-contract-export.yml | 74 --
.../schemas/dashboard-contracts.schema.json | 1105 +++++++++++++++++
dashboard/codegen/src/generate.ts | 2 +-
dashboard/src/contracts/decoders.ts | 401 ++++++
dashboard/src/contracts/types.ts | 440 +++++--
.../src/test/fixtures/settings-patch.json | 639 ++++++++++
.../settings/SettingsPage.dom.test.tsx | 40 +-
.../settings/SettingsReviewPanel.tsx | 5 +-
.../settings/settingsEditorMachine.ts | 4 +-
.../settings/settingsMutation.test.ts | 89 ++
.../workspaces/settings/settingsMutation.ts | 155 +--
11 files changed, 2673 insertions(+), 281 deletions(-)
delete mode 100644 .github/workflows/dashboard-contract-export.yml
create mode 100644 dashboard/src/test/fixtures/settings-patch.json
create mode 100644 dashboard/src/workspaces/settings/settingsMutation.test.ts
diff --git a/.github/workflows/dashboard-contract-export.yml b/.github/workflows/dashboard-contract-export.yml
deleted file mode 100644
index c9aeab7cda..0000000000
--- a/.github/workflows/dashboard-contract-export.yml
+++ /dev/null
@@ -1,74 +0,0 @@
-# Temporary build route: remove after importing the canonical generated JSON.
-name: Export dashboard contract artifacts
-run-name: Export dashboard contracts for ${{ github.event.pull_request.head.sha }}
-
-on:
- pull_request:
- types: [opened, synchronize, reopened]
-
-permissions:
- contents: read
-
-concurrency:
- group: dashboard-contract-export-${{ github.event.pull_request.number }}
- cancel-in-progress: true
-
-jobs:
- export:
- if: github.event.pull_request.head.repo.full_name == github.repository
- runs-on: ubuntu-latest
- timeout-minutes: 90
- env:
- CARGO_BAZEL_TIMEOUT: "3600"
- steps:
- - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- with:
- ref: ${{ github.event.pull_request.head.sha }}
- persist-credentials: false
-
- - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
- with:
- node-version: "26"
-
- - uses: ./.github/actions/setup-pnpm
-
- - uses: ./.github/actions/setup-bazel
- with:
- cache-key: ci-linux-test-v2
-
- - name: Export the canonical dashboard schema
- run: |
- set -euo pipefail
- EXPORT_DIRECTORY="$RUNNER_TEMP/dashboard-contract-export"
- mkdir -p "$EXPORT_DIRECTORY"
- bazel run --config=ci --jobs=1 //sdks/codegen:dashboard_schema -- "$EXPORT_DIRECTORY/dashboard-contracts.schema.json"
- test -s "$EXPORT_DIRECTORY/dashboard-contracts.schema.json"
-
- # Preserve the schema before the independent fixture build starts.
- - name: Upload the canonical dashboard schema
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
- with:
- name: dashboard-schema-${{ github.event.pull_request.head.sha }}
- path: ${{ runner.temp }}/dashboard-contract-export/dashboard-contracts.schema.json
- if-no-files-found: error
- retention-days: 1
-
- - name: Serialize the real settings response fixture
- run: |
- set -euo pipefail
- EXPORT_DIRECTORY="$RUNNER_TEMP/dashboard-contract-export"
- bazel test --config=ci --jobs=1 //crates/tracedecay-dashboard-api:unit_test \
- --test_arg=settings_api::tests::wire::project_patch_serialization_preserves_settlement_and_refusals \
- --test_arg=--exact --test_output=all --cache_test_results=no \
- --nozip_undeclared_test_outputs
- fixture=bazel-testlogs/crates/tracedecay-dashboard-api/unit_test/test.outputs/settings-patch.json
- test -s "$fixture"
- cp -- "$fixture" "$EXPORT_DIRECTORY/settings-patch.json"
-
- - name: Upload the canonical settings response fixture
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
- with:
- name: settings-patch-fixture-${{ github.event.pull_request.head.sha }}
- path: ${{ runner.temp }}/dashboard-contract-export/settings-patch.json
- if-no-files-found: error
- retention-days: 1
diff --git a/dashboard/codegen/schemas/dashboard-contracts.schema.json b/dashboard/codegen/schemas/dashboard-contracts.schema.json
index e65724f847..76951811fd 100644
--- a/dashboard/codegen/schemas/dashboard-contracts.schema.json
+++ b/dashboard/codegen/schemas/dashboard-contracts.schema.json
@@ -1343,6 +1343,74 @@
],
"type": "string"
},
+ "ApplicationOutcome": {
+ "description": "Canonical outcome family for an admitted application operation.",
+ "oneOf": [
+ {
+ "properties": {
+ "outcome": {
+ "const": "evidence",
+ "type": "string"
+ },
+ "value": {
+ "$ref": "#/$defs/EvidencePacket"
+ }
+ },
+ "required": [
+ "outcome",
+ "value"
+ ],
+ "type": "object"
+ },
+ {
+ "properties": {
+ "outcome": {
+ "const": "preview",
+ "type": "string"
+ },
+ "value": {
+ "$ref": "#/$defs/PreviewResult"
+ }
+ },
+ "required": [
+ "outcome",
+ "value"
+ ],
+ "type": "object"
+ },
+ {
+ "properties": {
+ "outcome": {
+ "const": "effect",
+ "type": "string"
+ },
+ "value": {
+ "$ref": "#/$defs/EffectResult"
+ }
+ },
+ "required": [
+ "outcome",
+ "value"
+ ],
+ "type": "object"
+ },
+ {
+ "description": "An operation-owned typed result that carries its own receipts, such as\na source-edit result with its durable effect, or a rendered document.",
+ "properties": {
+ "outcome": {
+ "const": "result",
+ "type": "string"
+ },
+ "value": true
+ },
+ "required": [
+ "outcome",
+ "value"
+ ],
+ "type": "object"
+ }
+ ]
+ },
"ApplicationProblemDetailV1": {
"description": "The structured facts behind a problem. Adapters read these fields; the\nproblem's `message` is only their one human rendering.",
"oneOf": [
@@ -1828,6 +1896,45 @@
"description": "Strongly typed canonical identity: `AttemptId`.",
"type": "string"
},
+ "AuthorityReceipt": {
+ "additionalProperties": false,
+ "description": "Proof that this request crossed the current authorization boundary.",
+ "properties": {
+ "authorized_scope_digest": {
+ "$ref": "#/$defs/ManifestDigest"
+ },
+ "disclosure": {
+ "$ref": "#/$defs/DisclosureClass"
+ },
+ "grant_digest": {
+ "$ref": "#/$defs/ManifestDigest"
+ },
+ "grant_id": {
+ "type": "string"
+ },
+ "grant_revision": {
+ "format": "uint64",
+ "minimum": 0,
+ "type": "integer"
+ },
+ "policy": {
+ "$ref": "#/$defs/PolicyDecisionRef"
+ },
+ "revalidated_at": {
+ "$ref": "#/$defs/UtcMicros"
+ }
+ },
+ "required": [
+ "grant_id",
+ "grant_revision",
+ "grant_digest",
+ "authorized_scope_digest",
+ "disclosure",
+ "policy",
+ "revalidated_at"
+ ],
+ "type": "object"
+ },
"AuthorizedRoot": {
"additionalProperties": false,
"description": "One exact application scope paired with its registered physical locator.\n\nThe scope remains the identity authority. The locator is retained only so\na later read or restart can reopen the exact registered root without an\nactive-graph or CWD fallback.",
@@ -3345,6 +3452,15 @@
],
"type": "object"
},
+ "BudgetClass": {
+ "description": "Bounded elapsed-work classification supplied by a retriever.",
+ "enum": [
+ "within_budget",
+ "approaching_limit",
+ "exhausted"
+ ],
+ "type": "string"
+ },
"CallChainMeasurementV1": {
"properties": {
"complete": {
@@ -3452,6 +3568,22 @@
],
"type": "object"
},
+ "CancellationObservation": {
+ "additionalProperties": false,
+ "properties": {
+ "observed_at": {
+ "$ref": "#/$defs/UtcMicros"
+ },
+ "stage": {
+ "$ref": "#/$defs/CancellationStage"
+ }
+ },
+ "required": [
+ "stage",
+ "observed_at"
+ ],
+ "type": "object"
+ },
"CancellationStage": {
"description": "Exact stage at which cancellation or deadline state was observed.",
"enum": [
@@ -3813,6 +3945,43 @@
"description": "Strongly typed canonical identity: `CodeGenerationId`.",
"type": "string"
},
+ "CodeGraphReadFreshnessV1": {
+ "description": "Freshness of the generation a verified graph read serves.",
+ "oneOf": [
+ {
+ "properties": {
+ "state": {
+ "const": "current",
+ "type": "string"
+ }
+ },
+ "required": [
+ "state"
+ ],
+ "type": "object"
+ },
+ {
+ "properties": {
+ "rebuild_in_flight": {
+ "type": "boolean"
+ },
+ "sealed_at": {
+ "$ref": "#/$defs/UtcMicros"
+ },
+ "state": {
+ "const": "last_complete_stale",
+ "type": "string"
+ }
+ },
+ "required": [
+ "state",
+ "sealed_at",
+ "rebuild_in_flight"
+ ],
+ "type": "object"
+ }
+ ]
+ },
"CodeGraphServingReadinessV1": {
"description": "Interactive graph-serving state for the latest sealed generation.\n\nA sealed generation can expose truthful census statistics before its graph\nprojection is ready to serve queries, so readiness is reported separately.",
"oneOf": [
@@ -4752,6 +4921,70 @@
"description": "Strongly typed canonical identity: `ComponentVersion`.",
"type": "string"
},
+ "ConfigurationRefusalV1": {
+ "description": "Configuration refusals carrying the same stable codes as the HTTP routes.",
+ "oneOf": [
+ {
+ "additionalProperties": false,
+ "properties": {
+ "actual_revision_id": {
+ "type": "string"
+ },
+ "code": {
+ "const": "configuration_revision_conflict",
+ "type": "string"
+ },
+ "detail": {
+ "type": "string"
+ },
+ "expected_revision_id": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "code",
+ "detail",
+ "expected_revision_id",
+ "actual_revision_id"
+ ],
+ "type": "object"
+ },
+ {
+ "additionalProperties": false,
+ "properties": {
+ "code": {
+ "const": "configuration_authority_unavailable",
+ "type": "string"
+ },
+ "detail": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "code",
+ "detail"
+ ],
+ "type": "object"
+ },
+ {
+ "additionalProperties": false,
+ "properties": {
+ "code": {
+ "const": "application_contract_violation",
+ "type": "string"
+ },
+ "detail": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "code",
+ "detail"
+ ],
+ "type": "object"
+ }
+ ]
+ },
"ConfigurationRevisionId": {
"description": "Strongly typed canonical identity: `ConfigurationRevisionId`.",
"type": "string"
@@ -5167,6 +5400,36 @@
}
]
},
+ "DashboardConfigurationRefusalV1": {
+ "anyOf": [
+ {
+ "additionalProperties": false,
+ "properties": {
+ "detail": {
+ "type": "string"
+ },
+ "validation_errors": {
+ "items": {
+ "$ref": "#/$defs/SettingsValidationIssueV1"
+ },
+ "type": "array"
+ }
+ },
+ "required": [
+ "detail",
+ "validation_errors"
+ ],
+ "type": "object"
+ },
+ {
+ "$ref": "#/$defs/ConfigurationRefusalV1"
+ },
+ {
+ "$ref": "#/$defs/ApplicationProblemEnvelope"
+ }
+ ],
+ "description": "Every refusal body produced by the dashboard configuration adapter. The\nuntagged wrapper preserves the existing validation, CAS, and application\nproblem wire formats while giving producers and consumers one authority."
+ },
"DashboardCoverageCompletenessV1": {
"description": "Coverage completeness axis. `Unsupported` distinguishes \"the source that\nwould establish coverage is not wired\" from `Unknown` (\"coverage could not\nbe determined\").",
"enum": [
@@ -6917,6 +7180,70 @@
],
"type": "object"
},
+ "DashboardEnvelopeV130": {
+ "description": "The normative read-model envelope. Every V2 read-model route returns exactly\nthis shape; only `payload` varies by route.",
+ "properties": {
+ "authorization": {
+ "$ref": "#/$defs/DashboardAuthorizationV1"
+ },
+ "coverage": {
+ "$ref": "#/$defs/DashboardCoverageV1"
+ },
+ "domain_state": {
+ "$ref": "#/$defs/DashboardDomainStateV1"
+ },
+ "freshness": {
+ "$ref": "#/$defs/DashboardFreshnessV1"
+ },
+ "legal_actions": {
+ "items": {
+ "$ref": "#/$defs/DashboardLegalActionRefV1"
+ },
+ "type": "array"
+ },
+ "payload": {
+ "$ref": "#/$defs/SettingsPayloadV1"
+ },
+ "schema_revision": {
+ "format": "uint32",
+ "minimum": 0,
+ "type": "integer"
+ },
+ "scope": {
+ "$ref": "#/$defs/DashboardScopeV1"
+ },
+ "source_watermark": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/DashboardWatermarkV1"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "time": {
+ "$ref": "#/$defs/DashboardTimeV1"
+ },
+ "version": {
+ "$ref": "#/$defs/DashboardVersionV1"
+ }
+ },
+ "required": [
+ "schema_revision",
+ "scope",
+ "version",
+ "time",
+ "source_watermark",
+ "authorization",
+ "coverage",
+ "freshness",
+ "domain_state",
+ "legal_actions",
+ "payload"
+ ],
+ "type": "object"
+ },
"DashboardEnvelopeV14": {
"description": "The normative read-model envelope. Every V2 read-model route returns exactly\nthis shape; only `payload` varies by route.",
"properties": {
@@ -7487,6 +7814,19 @@
],
"type": "object"
},
+ "Deadline": {
+ "additionalProperties": false,
+ "description": "One immutable deadline supplied by the caller or upstream admission layer.",
+ "properties": {
+ "expires_at": {
+ "$ref": "#/$defs/UtcMicros"
+ }
+ },
+ "required": [
+ "expires_at"
+ ],
+ "type": "object"
+ },
"DecideWorkProposalRequestV1": {
"additionalProperties": false,
"properties": {
@@ -11160,6 +11500,15 @@
],
"type": "object"
},
+ "DisclosureClass": {
+ "description": "Disclosure ceiling carried by an immutable grant and revalidated at sinks.",
+ "enum": [
+ "metadata",
+ "evidence",
+ "sensitive"
+ ],
+ "type": "string"
+ },
"DoctorCoverageCompletenessV1": {
"description": "Whether Doctor observed all of a family's evidence sources.",
"oneOf": [
@@ -11729,6 +12078,49 @@
],
"type": "object"
},
+ "EffectResult": {
+ "additionalProperties": false,
+ "description": "Result of an admitted effect. `EffectUnknown` remains a receipt state and\ncannot be remapped into a pre-admission problem.",
+ "properties": {
+ "authority": {
+ "$ref": "#/$defs/AuthorityReceipt"
+ },
+ "effect_class": {
+ "$ref": "#/$defs/EffectClass"
+ },
+ "effect_id": {
+ "type": "string"
+ },
+ "execution": {
+ "$ref": "#/$defs/OperationReceipt"
+ },
+ "expected_state": {
+ "$ref": "#/$defs/ManifestDigest"
+ },
+ "idempotency_key": {
+ "type": "string"
+ },
+ "payload": true,
+ "receipt": {
+ "$ref": "#/$defs/EffectReceipt"
+ },
+ "reconciliation": {
+ "$ref": "#/$defs/ReconciliationState"
+ }
+ },
+ "required": [
+ "effect_id",
+ "effect_class",
+ "idempotency_key",
+ "authority",
+ "expected_state",
+ "execution",
+ "reconciliation",
+ "receipt",
+ "payload"
+ ],
+ "type": "object"
+ },
"EffectTermination": {
"description": "Durable effect receipt terminal state.",
"enum": [
@@ -11793,6 +12185,46 @@
],
"type": "object"
},
+ "EvidenceAuthority": {
+ "additionalProperties": false,
+ "description": "Evidence authority is separate from caller authority and cannot grant access.",
+ "properties": {
+ "evidence_id": {
+ "type": "string"
+ },
+ "horizon": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/UtcMicros"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "producer": {
+ "type": "string"
+ },
+ "revision": {
+ "$ref": "#/$defs/ComponentVersion"
+ },
+ "scope": {
+ "$ref": "#/$defs/ResolvedScope"
+ },
+ "source_kind": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "evidence_id",
+ "source_kind",
+ "producer",
+ "scope",
+ "revision",
+ "horizon"
+ ],
+ "type": "object"
+ },
"EvidenceCoverage": {
"additionalProperties": false,
"description": "Deterministic coverage fold input for an evidence packet.",
@@ -11858,6 +12290,184 @@
],
"type": "string"
},
+ "EvidencePacket": {
+ "additionalProperties": false,
+ "description": "Immutable evidence packet consumed by adapters and later planner work.",
+ "properties": {
+ "authority": {
+ "$ref": "#/$defs/AuthorityReceipt"
+ },
+ "contributions": {
+ "items": {
+ "$ref": "#/$defs/RetrieverContribution"
+ },
+ "type": "array"
+ },
+ "coverage": {
+ "$ref": "#/$defs/EvidenceCoverage"
+ },
+ "evidence_authorities": {
+ "items": {
+ "$ref": "#/$defs/EvidenceAuthority"
+ },
+ "type": "array"
+ },
+ "execution": {
+ "$ref": "#/$defs/OperationReceipt"
+ },
+ "omissions": {
+ "items": {
+ "$ref": "#/$defs/Omission"
+ },
+ "type": "array"
+ },
+ "page": {
+ "$ref": "#/$defs/PageState"
+ },
+ "payload": true,
+ "scores": {
+ "items": {
+ "$ref": "#/$defs/EvidenceScore"
+ },
+ "type": "array"
+ },
+ "temporal": {
+ "$ref": "#/$defs/TemporalState"
+ }
+ },
+ "required": [
+ "temporal",
+ "authority",
+ "evidence_authorities",
+ "coverage",
+ "omissions",
+ "scores",
+ "contributions",
+ "page",
+ "execution",
+ "payload"
+ ],
+ "type": "object"
+ },
+ "EvidenceScore": {
+ "additionalProperties": false,
+ "properties": {
+ "calibration_revision": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/ComponentVersion"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "calibration_valid": {
+ "type": [
+ "boolean",
+ "null"
+ ]
+ },
+ "deterministic_components": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "kind": {
+ "$ref": "#/$defs/EvidenceScoreKind"
+ },
+ "score_id": {
+ "type": "string"
+ },
+ "value": {
+ "$ref": "#/$defs/EvidenceScoreValue"
+ }
+ },
+ "required": [
+ "score_id",
+ "kind",
+ "value",
+ "calibration_revision",
+ "calibration_valid",
+ "deterministic_components"
+ ],
+ "type": "object"
+ },
+ "EvidenceScoreKind": {
+ "description": "Evidence score semantics. Scores are metadata, never authority.",
+ "enum": [
+ "ordinal_rank",
+ "heuristic_score",
+ "calibrated_probability",
+ "calibrated_interval"
+ ],
+ "type": "string"
+ },
+ "EvidenceScoreValue": {
+ "oneOf": [
+ {
+ "properties": {
+ "kind": {
+ "const": "ordinal",
+ "type": "string"
+ },
+ "rank": {
+ "format": "uint64",
+ "minimum": 0,
+ "type": "integer"
+ }
+ },
+ "required": [
+ "kind",
+ "rank"
+ ],
+ "type": "object"
+ },
+ {
+ "properties": {
+ "kind": {
+ "const": "fixed_point",
+ "type": "string"
+ },
+ "micros": {
+ "format": "uint64",
+ "minimum": 0,
+ "type": "integer"
+ }
+ },
+ "required": [
+ "kind",
+ "micros"
+ ],
+ "type": "object"
+ },
+ {
+ "properties": {
+ "kind": {
+ "const": "interval",
+ "type": "string"
+ },
+ "lower_micros": {
+ "format": "uint64",
+ "minimum": 0,
+ "type": "integer"
+ },
+ "upper_micros": {
+ "format": "uint64",
+ "minimum": 0,
+ "type": "integer"
+ }
+ },
+ "required": [
+ "kind",
+ "lower_micros",
+ "upper_micros"
+ ],
+ "type": "object"
+ }
+ ]
+ },
"ExecutionBlockedCauseV1": {
"description": "Cause a work item was blocked for.",
"enum": [
@@ -14461,6 +15071,15 @@
"description": "Strongly typed canonical identity: `FileOccurrenceId`.",
"type": "string"
},
+ "FreshnessState": {
+ "description": "Application-level freshness. Missing or partial truth never becomes current.",
+ "enum": [
+ "current",
+ "stale",
+ "unknown"
+ ],
+ "type": "string"
+ },
"GenerateProposalRequest": {
"additionalProperties": false,
"description": "Read-only proposal generation over one exact current product graph.",
@@ -20495,6 +21114,225 @@
],
"type": "object"
},
+ "Omission": {
+ "additionalProperties": false,
+ "properties": {
+ "count": {
+ "format": "uint64",
+ "minimum": 0,
+ "type": "integer"
+ },
+ "domain": {
+ "$ref": "#/$defs/EvidenceDomain"
+ },
+ "reason": {
+ "$ref": "#/$defs/OmissionReason"
+ }
+ },
+ "required": [
+ "domain",
+ "count",
+ "reason"
+ ],
+ "type": "object"
+ },
+ "OmissionReason": {
+ "description": "Safe reason why authorized requested evidence was omitted.",
+ "oneOf": [
+ {
+ "enum": [
+ "budget",
+ "redacted",
+ "unavailable",
+ "unsupported",
+ "stale",
+ "failed",
+ "cancelled",
+ "timed_out",
+ "conflict"
+ ],
+ "type": "string"
+ },
+ {
+ "const": "macro_body_unparsed",
+ "description": "Evidence came from a macro body that was not expanded, so what the\nexpansion defines or calls is not covered.",
+ "type": "string"
+ },
+ {
+ "const": "import_unmodeled",
+ "description": "A caller sits under a `use` whose binding the index could not model\n(a glob in a block or inline module), so whether it calls the target\nis not covered.",
+ "type": "string"
+ },
+ {
+ "const": "cursor_expired",
+ "description": "The continuation cursor can no longer be redeemed (its lifetime ended\nor the snapshot it pages is gone); restart the request without it.",
+ "type": "string"
+ },
+ {
+ "const": "cursor_foreign",
+ "description": "The continuation cursor was issued for another project, worktree, or\nref; redeem it where it was issued.",
+ "type": "string"
+ }
+ ]
+ },
+ "OperationBudgetUsage": {
+ "additionalProperties": false,
+ "description": "Bounded work accounting supplied by an owning port or transaction.",
+ "properties": {
+ "bytes_consumed": {
+ "format": "uint64",
+ "minimum": 0,
+ "type": "integer"
+ },
+ "elapsed_micros": {
+ "format": "uint64",
+ "minimum": 0,
+ "type": "integer"
+ },
+ "units_consumed": {
+ "format": "uint64",
+ "minimum": 0,
+ "type": "integer"
+ }
+ },
+ "required": [
+ "units_consumed",
+ "bytes_consumed",
+ "elapsed_micros"
+ ],
+ "type": "object"
+ },
+ "OperationReceipt": {
+ "additionalProperties": false,
+ "description": "Canonical operation evidence. An admitted failure remains represented here\nrather than being replaced by a transport exception.",
+ "properties": {
+ "budget": {
+ "$ref": "#/$defs/OperationBudgetUsage"
+ },
+ "cancellation": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/CancellationObservation"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "effective_deadline": {
+ "$ref": "#/$defs/Deadline"
+ },
+ "ended_at": {
+ "$ref": "#/$defs/UtcMicros"
+ },
+ "started_at": {
+ "$ref": "#/$defs/UtcMicros"
+ },
+ "termination": {
+ "$ref": "#/$defs/OperationTermination"
+ }
+ },
+ "required": [
+ "started_at",
+ "ended_at",
+ "effective_deadline",
+ "cancellation",
+ "budget",
+ "termination"
+ ],
+ "type": "object"
+ },
+ "OperationTermination": {
+ "description": "Terminal state after an operation has been admitted.",
+ "enum": [
+ "completed",
+ "cancelled",
+ "timed_out",
+ "failed",
+ "unavailable",
+ "partial",
+ "effect_unknown"
+ ],
+ "type": "string"
+ },
+ "PageCursor": {
+ "description": "Exact continuation authority for the enclosing evidence page: an opaque\ncursor bound to the operation and request that minted it.",
+ "oneOf": [
+ {
+ "additionalProperties": false,
+ "properties": {
+ "cursor": {
+ "type": "string"
+ },
+ "kind": {
+ "const": "opaque",
+ "type": "string"
+ }
+ },
+ "required": [
+ "kind",
+ "cursor"
+ ],
+ "type": "object"
+ }
+ ]
+ },
+ "PageState": {
+ "additionalProperties": false,
+ "description": "Stable page state. General cursor bytes stay opaque until authorization is\nrevalidated; structural fact cursors retain their canonical ordering type.",
+ "properties": {
+ "cursor": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/PageCursor"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "expires_at": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/UtcMicros"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "returned": {
+ "format": "uint64",
+ "minimum": 0,
+ "type": "integer"
+ },
+ "sort_contract_id": {
+ "$ref": "#/$defs/SortContractId"
+ },
+ "sort_revision": {
+ "format": "uint32",
+ "minimum": 0,
+ "type": "integer"
+ },
+ "total": {
+ "format": "uint64",
+ "minimum": 0,
+ "type": [
+ "integer",
+ "null"
+ ]
+ }
+ },
+ "required": [
+ "sort_contract_id",
+ "sort_revision",
+ "total",
+ "returned",
+ "cursor",
+ "expires_at"
+ ],
+ "type": "object"
+ },
"PauseWorkRunCommand": {
"additionalProperties": false,
"properties": {
@@ -20675,6 +21513,33 @@
],
"type": "object"
},
+ "PolicyDecisionRef": {
+ "additionalProperties": false,
+ "description": "A policy decision pinned into a receipt or provider identity.",
+ "properties": {
+ "decision_id": {
+ "type": "string"
+ },
+ "digest": {
+ "$ref": "#/$defs/ManifestDigest"
+ },
+ "evaluator_revision": {
+ "$ref": "#/$defs/ComponentVersion"
+ },
+ "revision": {
+ "format": "uint64",
+ "minimum": 0,
+ "type": "integer"
+ }
+ },
+ "required": [
+ "decision_id",
+ "revision",
+ "digest",
+ "evaluator_revision"
+ ],
+ "type": "object"
+ },
"PolicyIdentifierV1": {
"description": "A bounded, canonical identifier owned by the policy input schema.\n\nIt represents immutable references only; it is never a path, display\nlabel, provider account, branch name, or native object identifier.",
"type": "string"
@@ -20774,6 +21639,41 @@
],
"type": "object"
},
+ "PreviewResult": {
+ "additionalProperties": false,
+ "description": "Read-only preview of a future typed effect.",
+ "properties": {
+ "authority": {
+ "$ref": "#/$defs/AuthorityReceipt"
+ },
+ "effect_class": {
+ "$ref": "#/$defs/EffectClass"
+ },
+ "execution": {
+ "$ref": "#/$defs/OperationReceipt"
+ },
+ "expected_state": {
+ "$ref": "#/$defs/ManifestDigest"
+ },
+ "payload": true,
+ "preview_digest": {
+ "$ref": "#/$defs/ManifestDigest"
+ },
+ "preview_id": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "preview_id",
+ "preview_digest",
+ "effect_class",
+ "authority",
+ "expected_state",
+ "execution",
+ "payload"
+ ],
+ "type": "object"
+ },
"PrimitiveFreshnessStateV1": {
"description": "Whether the served code generation is known current at serve time.",
"enum": [
@@ -21300,6 +22200,29 @@
],
"type": "object"
},
+ "ProjectSettingsPatchResponseV1": {
+ "properties": {
+ "application_outcome": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/ApplicationOutcome"
+ },
+ {
+ "type": "null"
+ }
+ ],
+ "description": "Exact application settlement returned by the configuration binding.\n`None` means the submitted patch was already the current state."
+ },
+ "current": {
+ "$ref": "#/$defs/DashboardEnvelopeV130"
+ }
+ },
+ "required": [
+ "application_outcome",
+ "current"
+ ],
+ "type": "object"
+ },
"ProjectSettingsPayloadV1": {
"properties": {
"config": {
@@ -21768,6 +22691,15 @@
],
"type": "string"
},
+ "ReconciliationState": {
+ "description": "Reconciliation state retained after an admitted effect.",
+ "enum": [
+ "pending",
+ "reconciled",
+ "failed"
+ ],
+ "type": "string"
+ },
"RecordWorkHandoffRequestV1": {
"additionalProperties": false,
"properties": {
@@ -22514,6 +23446,94 @@
"description": "Strongly typed canonical identity: `RetrievalAnchorId`.",
"type": "string"
},
+ "RetrieverContribution": {
+ "additionalProperties": false,
+ "description": "One source-owned contribution to the packet fold.",
+ "properties": {
+ "contract": {
+ "$ref": "#/$defs/ResultContractRef"
+ },
+ "coverage": {
+ "$ref": "#/$defs/EvidenceCoverage"
+ },
+ "domain": {
+ "$ref": "#/$defs/EvidenceDomain"
+ },
+ "elapsed_budget_class": {
+ "$ref": "#/$defs/BudgetClass"
+ },
+ "evidence_authorities": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "omitted_count": {
+ "format": "uint64",
+ "minimum": 0,
+ "type": "integer"
+ },
+ "producer_revision": {
+ "$ref": "#/$defs/ComponentVersion"
+ },
+ "provenance_anchors": {
+ "items": {
+ "$ref": "#/$defs/RetrievalAnchorId"
+ },
+ "type": "array"
+ },
+ "retriever_id": {
+ "$ref": "#/$defs/RetrieverId"
+ },
+ "returned_count": {
+ "format": "uint64",
+ "minimum": 0,
+ "type": "integer"
+ },
+ "score_ids": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "state": {
+ "$ref": "#/$defs/RetrieverContributionState"
+ }
+ },
+ "required": [
+ "retriever_id",
+ "contract",
+ "producer_revision",
+ "domain",
+ "state",
+ "coverage",
+ "returned_count",
+ "omitted_count",
+ "score_ids",
+ "provenance_anchors",
+ "evidence_authorities",
+ "elapsed_budget_class"
+ ],
+ "type": "object"
+ },
+ "RetrieverContributionState": {
+ "description": "Terminal state reported by one retriever contribution.",
+ "enum": [
+ "completed",
+ "partial",
+ "unavailable",
+ "unsupported",
+ "stale",
+ "failed",
+ "cancelled",
+ "timed_out"
+ ],
+ "type": "string"
+ },
+ "RetrieverId": {
+ "description": "Stable, canonical catalog identity for `RetrieverId`.",
+ "type": "string"
+ },
"RetryDirective": {
"description": "Safe adapter-independent retry instruction. Adapters preserve it verbatim.",
"enum": [
@@ -24500,6 +25520,22 @@
],
"type": "object"
},
+ "SettingsValidationIssueV1": {
+ "description": "One field-level settings validation failure.",
+ "properties": {
+ "field": {
+ "type": "string"
+ },
+ "message": {
+ "type": "string"
+ }
+ },
+ "required": [
+ "field",
+ "message"
+ ],
+ "type": "object"
+ },
"SharedProfileStoreLocatorV1": {
"additionalProperties": false,
"description": "Logical and verified physical identity of the shared Profile shard.\n\nBrain and profile IDs select the shard; the verified store locator binds its\nphysical store. Lease incarnations and authority epochs remain runtime fences.",
@@ -24856,6 +25892,10 @@
],
"type": "string"
},
+ "SortContractId": {
+ "description": "Stable, canonical catalog identity for `SortContractId`.",
+ "type": "string"
+ },
"SourceSpan": {
"additionalProperties": false,
"description": "Byte range inside one sanitized source file. Mutable line numbers are\nnever part of identity.",
@@ -26364,6 +27404,63 @@
}
]
},
+ "TemporalState": {
+ "additionalProperties": false,
+ "description": "Temporal provenance for a packet.",
+ "properties": {
+ "code_graph_freshness": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/CodeGraphReadFreshnessV1"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "freshness": {
+ "$ref": "#/$defs/FreshnessState"
+ },
+ "requested_at": {
+ "$ref": "#/$defs/UtcMicros"
+ },
+ "requested_mode": {
+ "$ref": "#/$defs/TemporalModeV1"
+ },
+ "resolved_at": {
+ "$ref": "#/$defs/UtcMicros"
+ },
+ "source_generation": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/CodeGenerationId"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "watermark_digest": {
+ "anyOf": [
+ {
+ "$ref": "#/$defs/ManifestDigest"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "required": [
+ "requested_mode",
+ "requested_at",
+ "resolved_at",
+ "source_generation",
+ "watermark_digest",
+ "freshness"
+ ],
+ "type": "object"
+ },
"TestMapMeasurementV1": {
"properties": {
"algorithm": {
@@ -35946,6 +37043,12 @@
"settings_project_patch": {
"$ref": "#/$defs/ProjectSettingsPatch"
},
+ "settings_project_patch_response": {
+ "$ref": "#/$defs/ProjectSettingsPatchResponseV1"
+ },
+ "settings_refusal": {
+ "$ref": "#/$defs/DashboardConfigurationRefusalV1"
+ },
"settings_user_patch": {
"$ref": "#/$defs/UserSettingsPatch"
},
@@ -36198,6 +37301,8 @@
"code_index_freshness",
"settings",
"settings_project_patch",
+ "settings_project_patch_response",
+ "settings_refusal",
"settings_user_patch",
"settings_code_index_worker_patch",
"observatory",
diff --git a/dashboard/codegen/src/generate.ts b/dashboard/codegen/src/generate.ts
index 1c3d4fc890..9524e0ca05 100644
--- a/dashboard/codegen/src/generate.ts
+++ b/dashboard/codegen/src/generate.ts
@@ -449,7 +449,7 @@ function emitNamedDef(
function emitAlias(desc: string, name: string, ann: string, zod: string, ts: string): EmittedDef {
return {
typeName: name,
- types: `export type ${name} = ${ts};`,
+ types: `export type ${name} =${ts.startsWith("\n") ? "" : " "}${ts};`,
decoders: `${desc}export const ${name}Schema: ${ann} = ${zod};`,
};
}
diff --git a/dashboard/src/contracts/decoders.ts b/dashboard/src/contracts/decoders.ts
index 67b2c136d4..a9d127776f 100644
--- a/dashboard/src/contracts/decoders.ts
+++ b/dashboard/src/contracts/decoders.ts
@@ -54,6 +54,7 @@ import type {
AnalyticsUsageCategoryV1,
AnalyticsUsageSummaryV1,
ApplicationExecutionFailureClassV1,
+ ApplicationOutcome,
ApplicationProblemDetailV1,
ApplicationProblemEnvelope,
ApplicationProblemKind,
@@ -61,6 +62,7 @@ import type {
ApplicationUnavailableClassV1,
ApplyWorkRelationReplanRequestV1,
AttemptId,
+ AuthorityReceipt,
AuthorizedRoot,
AuthorizedScopeSet,
AuthorizedWorkProductScopeV1,
@@ -104,8 +106,10 @@ import type {
BranchNamingPolicyV1,
BranchTopologyKindV1,
BranchTopologyPolicyV1,
+ BudgetClass,
CallChainMeasurementV1,
CallChainStepV1,
+ CancellationObservation,
CancellationStage,
CancelWorkAttemptCommand,
CanonicalGitRefNameV1,
@@ -118,6 +122,7 @@ import type {
CodeCloneIndexResourcesV1,
CodeCloneIndexStatusV1,
CodeGenerationId,
+ CodeGraphReadFreshnessV1,
CodeGraphServingReadinessV1,
CodeIndexBuildBlockedReasonV1,
CodeIndexBuildPhaseV1,
@@ -143,6 +148,7 @@ import type {
ComparisonDispositionV1,
ComplexityAnalysisV1,
ComponentVersion,
+ ConfigurationRefusalV1,
ConfigurationRevisionId,
ConfigurationSnapshotId,
ContentDigest,
@@ -160,6 +166,7 @@ import type {
CrossMergePolicyV1,
DaemonServiceUnitObservationV1,
DashboardAuthorizationV1,
+ DashboardConfigurationRefusalV1,
DashboardCoverageCompletenessV1,
DashboardCoverageV1,
DashboardDomainStateV1,
@@ -172,6 +179,7 @@ import type {
DashboardTimeV1,
DashboardVersionV1,
DashboardWatermarkV1,
+ Deadline,
DecideWorkProposalRequestV1,
DecideWorkRelationReplanRequestV1,
DecisionEngineSettingsV1,
@@ -244,6 +252,7 @@ import type {
DeliverySharedCodeRefStateV1,
DeliverySharedCodeRefV1,
DiagnosticsSearchedTsconfigV1,
+ DisclosureClass,
DoctorCoverageCompletenessV1,
DoctorCoverageStatementV1,
DoctorEvidenceReferenceV1,
@@ -262,11 +271,17 @@ import type {
DuplicateEffortKindV1,
EffectClass,
EffectReceipt,
+ EffectResult,
EffectTermination,
EnvironmentSettingsPayloadV1,
EnvironmentVariableV1,
+ EvidenceAuthority,
EvidenceCoverage,
EvidenceDomain,
+ EvidencePacket,
+ EvidenceScore,
+ EvidenceScoreKind,
+ EvidenceScoreValue,
ExecutionBlockedCauseV1,
ExecutionConcurrencyPhaseV1,
ExecutionConflictKindV1,
@@ -352,6 +367,7 @@ import type {
FeedbackSystemMetricV1,
FeedbackSystemQualityReadModelV1,
FileOccurrenceId,
+ FreshnessState,
GeneratedWorkProposal,
GenerateProposalRequest,
GitHubStackedPullRequestPolicyV1,
@@ -516,16 +532,25 @@ import type {
ObservabilityHorizonV1,
ObservationSourceIdentityV1,
ObservatoryReadModelV1,
+ Omission,
+ OmissionReason,
+ OperationBudgetUsage,
+ OperationReceipt,
+ OperationTermination,
+ PageCursor,
+ PageState,
PauseWorkRunCommand,
PayloadAccessState,
PayloadDigestV1,
PayloadReferenceV1,
PerformanceComparisonReadModelV1,
+ PolicyDecisionRef,
PolicyIdentifierV1,
PrAutoTrackEntryV1,
PrAutoTrackPayloadV1,
PrepareWorkDuplicateAdjudicationRequestV1,
PrepareWorkProductMutationRequestV1,
+ PreviewResult,
PrimitiveFreshnessStateV1,
PrimitiveIndexingStateV1,
PrimitiveSearchFreshnessV1,
@@ -543,6 +568,7 @@ import type {
ProjectRegistrySummary,
ProjectRepoGroup,
ProjectSettingsPatch,
+ ProjectSettingsPatchResponseV1,
ProjectSettingsPayloadV1,
ProjectsPayloadV1,
ProposalId,
@@ -560,6 +586,7 @@ import type {
ProximityWarningClassV1,
PublicCodeProject,
QuantityEvidenceClassV1,
+ ReconciliationState,
RecordWorkHandoffRequestV1,
RefId,
RegisteredRootLocatorV1,
@@ -592,6 +619,9 @@ import type {
ResumeWorkAttemptsCommand,
ResumeWorkRunCommand,
RetrievalAnchorId,
+ RetrieverContribution,
+ RetrieverContributionState,
+ RetrieverId,
RetryDirective,
RetryScope,
RetryWorkAttemptCommandV1,
@@ -654,6 +684,7 @@ import type {
SessionRetrievalStructuralRefusalV1,
SettingsAvailabilityV1,
SettingsPayloadV1,
+ SettingsValidationIssueV1,
SharedProfileStoreLocatorV1,
SignificantTableGrowthSampleV1,
SimilarCoverageV1,
@@ -664,6 +695,7 @@ import type {
SkillInstallTarget,
SkillOutcomeRecord,
SkillOutcomeVerdict,
+ SortContractId,
SourceSpan,
SourceStoreId,
StartWorkAttemptCommand,
@@ -701,6 +733,7 @@ import type {
TelemetrySettingsPatch,
TelemetrySettingsV1,
TemporalModeV1,
+ TemporalState,
TestMapMeasurementV1,
TokenActualV1,
TokenPairV1,
@@ -1506,6 +1539,21 @@ export const AnalyticsUsageSummaryV1Schema: z.ZodObject<{
/** Stable non-retryable class for an admitted execution failure. */
export const ApplicationExecutionFailureClassV1Schema: z.ZodEnum<{ "denied": "denied"; "malformed_output": "malformed_output"; "permanent": "permanent" }> = z.enum(["denied", "malformed_output", "permanent"]);
+/** Canonical outcome family for an admitted application operation. */
+export const ApplicationOutcomeSchema: z.ZodType = z.discriminatedUnion("outcome", [z.object({
+ outcome: z.literal("effect"),
+ value: z.lazy(() => EffectResultSchema),
+}), z.object({
+ outcome: z.literal("evidence"),
+ value: z.lazy(() => EvidencePacketSchema),
+}), z.object({
+ outcome: z.literal("preview"),
+ value: z.lazy(() => PreviewResultSchema),
+}), z.object({
+ outcome: z.literal("result"),
+ value: z.unknown(),
+})]);
+
/** The structured facts behind a problem. Adapters read these fields; the
problem's `message` is only their one human rendering. */
export const ApplicationProblemDetailV1Schema: z.ZodType = z.discriminatedUnion("kind", [z.object({
@@ -1635,6 +1683,25 @@ export const ApplyWorkRelationReplanRequestV1Schema: z.ZodObject<{
/** Strongly typed canonical identity: `AttemptId`. */
export const AttemptIdSchema: z.ZodType = z.string();
+/** Proof that this request crossed the current authorization boundary. */
+export const AuthorityReceiptSchema: z.ZodObject<{
+ authorized_scope_digest: z.ZodType;
+ disclosure: z.ZodType;
+ grant_digest: z.ZodType;
+ grant_id: z.ZodType;
+ grant_revision: z.ZodType;
+ policy: z.ZodType;
+ revalidated_at: z.ZodType;
+}, z.core.$strict> = z.object({
+ authorized_scope_digest: z.lazy(() => ManifestDigestSchema),
+ disclosure: z.lazy(() => DisclosureClassSchema),
+ grant_digest: z.lazy(() => ManifestDigestSchema),
+ grant_id: z.string(),
+ grant_revision: z.number().int().min(0),
+ policy: z.lazy(() => PolicyDecisionRefSchema),
+ revalidated_at: z.lazy(() => UtcMicrosSchema),
+}).strict();
+
/** One exact application scope paired with its registered physical locator.
The scope remains the identity authority. The locator is retained only so
@@ -2201,6 +2268,9 @@ export const BranchTopologyPolicyV1Schema: z.ZodObject<{
allowed: z.array(z.lazy(() => BranchTopologyKindV1Schema)),
}).strict();
+/** Bounded elapsed-work classification supplied by a retriever. */
+export const BudgetClassSchema: z.ZodEnum<{ "approaching_limit": "approaching_limit"; "exhausted": "exhausted"; "within_budget": "within_budget" }> = z.enum(["approaching_limit", "exhausted", "within_budget"]);
+
export const CallChainMeasurementV1Schema: z.ZodObject<{
complete: z.ZodType;
directed: z.ZodType;
@@ -2233,6 +2303,14 @@ export const CallChainStepV1Schema: z.ZodObject<{
node: z.lazy(() => NodeRefV1Schema),
});
+export const CancellationObservationSchema: z.ZodObject<{
+ observed_at: z.ZodType;
+ stage: z.ZodType;
+}, z.core.$strict> = z.object({
+ observed_at: z.lazy(() => UtcMicrosSchema),
+ stage: z.lazy(() => CancellationStageSchema),
+}).strict();
+
/** Exact stage at which cancellation or deadline state was observed. */
export const CancellationStageSchema: z.ZodEnum<{ "after_commit": "after_commit"; "before_admission": "before_admission"; "before_effect": "before_effect"; "before_read": "before_read"; "during_read": "during_read"; "effect_in_flight": "effect_in_flight"; "reconciling": "reconciling" }> = z.enum(["after_commit", "before_admission", "before_effect", "before_read", "during_read", "effect_in_flight", "reconciling"]);
@@ -2358,6 +2436,15 @@ export const CodeCloneIndexStatusV1Schema: z.ZodType = z.string();
+/** Freshness of the generation a verified graph read serves. */
+export const CodeGraphReadFreshnessV1Schema: z.ZodType = z.discriminatedUnion("state", [z.object({
+ state: z.literal("current"),
+}), z.object({
+ rebuild_in_flight: z.boolean(),
+ sealed_at: z.lazy(() => UtcMicrosSchema),
+ state: z.literal("last_complete_stale"),
+})]);
+
/** Interactive graph-serving state for the latest sealed generation.
A sealed generation can expose truthful census statistics before its graph
@@ -2677,6 +2764,20 @@ export const ComplexityAnalysisV1Schema: z.ZodUnion<[z.ZodLiteral<"complete">, z
/** Strongly typed canonical identity: `ComponentVersion`. */
export const ComponentVersionSchema: z.ZodType = z.string();
+/** Configuration refusals carrying the same stable codes as the HTTP routes. */
+export const ConfigurationRefusalV1Schema: z.ZodType = z.discriminatedUnion("code", [z.object({
+ code: z.literal("application_contract_violation"),
+ detail: z.string(),
+}).strict(), z.object({
+ code: z.literal("configuration_authority_unavailable"),
+ detail: z.string(),
+}).strict(), z.object({
+ actual_revision_id: z.string(),
+ code: z.literal("configuration_revision_conflict"),
+ detail: z.string(),
+ expected_revision_id: z.string(),
+}).strict()]);
+
/** Strongly typed canonical identity: `ConfigurationRevisionId`. */
export const ConfigurationRevisionIdSchema: z.ZodType = z.string();
@@ -2830,6 +2931,17 @@ export const DashboardAuthorizationV1Schema: z.ZodType;
+} | ConfigurationRefusalV1 | ApplicationProblemEnvelope, unknown> = z.union([z.object({
+ detail: z.string(),
+ validation_errors: z.array(z.lazy(() => SettingsValidationIssueV1Schema)),
+}).strict(), z.lazy(() => ConfigurationRefusalV1Schema), z.lazy(() => ApplicationProblemEnvelopeSchema)]);
+
/** Coverage completeness axis. `Unsupported` distinguishes "the source that
would establish coverage is not wired" from `Unknown` ("coverage could not
be determined"). */
@@ -2968,6 +3080,13 @@ export const DashboardWatermarkV1Schema: z.ZodObject<{
watermark: z.string(),
});
+/** One immutable deadline supplied by the caller or upstream admission layer. */
+export const DeadlineSchema: z.ZodObject<{
+ expires_at: z.ZodType;
+}, z.core.$strict> = z.object({
+ expires_at: z.lazy(() => UtcMicrosSchema),
+}).strict();
+
export const DecideWorkProposalRequestV1Schema: z.ZodObject<{
disposition: z.ZodType;
mutation: z.ZodType;
@@ -3969,6 +4088,9 @@ export const DiagnosticsSearchedTsconfigV1Schema: z.ZodObject<{
present: z.boolean(),
}).strict();
+/** Disclosure ceiling carried by an immutable grant and revalidated at sinks. */
+export const DisclosureClassSchema: z.ZodEnum<{ "evidence": "evidence"; "metadata": "metadata"; "sensitive": "sensitive" }> = z.enum(["evidence", "metadata", "sensitive"]);
+
/** Whether Doctor observed all of a family's evidence sources. */
export const DoctorCoverageCompletenessV1Schema: z.ZodUnion<[z.ZodLiteral<"complete">, z.ZodLiteral<"partial">, z.ZodLiteral<"unknown">]> = z.union([z.literal("complete"), z.literal("partial"), z.literal("unknown")]);
@@ -4153,6 +4275,30 @@ export const EffectReceiptSchema: z.ZodObject<{
scope: z.lazy(() => ResolvedScopeSchema),
}).strict();
+/** Result of an admitted effect. `EffectUnknown` remains a receipt state and
+cannot be remapped into a pre-admission problem. */
+export const EffectResultSchema: z.ZodObject<{
+ authority: z.ZodType;
+ effect_class: z.ZodType;
+ effect_id: z.ZodType;
+ execution: z.ZodType;
+ expected_state: z.ZodType;
+ idempotency_key: z.ZodType;
+ payload: z.ZodType;
+ receipt: z.ZodType;
+ reconciliation: z.ZodType;
+}, z.core.$strict> = z.object({
+ authority: z.lazy(() => AuthorityReceiptSchema),
+ effect_class: z.lazy(() => EffectClassSchema),
+ effect_id: z.string(),
+ execution: z.lazy(() => OperationReceiptSchema),
+ expected_state: z.lazy(() => ManifestDigestSchema),
+ idempotency_key: z.string(),
+ payload: z.unknown(),
+ receipt: z.lazy(() => EffectReceiptSchema),
+ reconciliation: z.lazy(() => ReconciliationStateSchema),
+}).strict();
+
/** Durable effect receipt terminal state. */
export const EffectTerminationSchema: z.ZodEnum<{ "cancelled": "cancelled"; "completed": "completed"; "effect_unknown": "effect_unknown"; "failed": "failed"; "partial": "partial"; "timed_out": "timed_out" }> = z.enum(["cancelled", "completed", "effect_unknown", "failed", "partial", "timed_out"]);
@@ -4180,6 +4326,23 @@ export const EnvironmentVariableV1Schema: z.ZodObject<{
value: z.string().nullable(),
});
+/** Evidence authority is separate from caller authority and cannot grant access. */
+export const EvidenceAuthoritySchema: z.ZodObject<{
+ evidence_id: z.ZodType;
+ horizon: z.ZodType;
+ producer: z.ZodType;
+ revision: z.ZodType;
+ scope: z.ZodType;
+ source_kind: z.ZodType;
+}, z.core.$strict> = z.object({
+ evidence_id: z.string(),
+ horizon: z.union([z.lazy(() => UtcMicrosSchema), z.null()]),
+ producer: z.string(),
+ revision: z.lazy(() => ComponentVersionSchema),
+ scope: z.lazy(() => ResolvedScopeSchema),
+ source_kind: z.string(),
+}).strict();
+
/** Deterministic coverage fold input for an evidence packet. */
export const EvidenceCoverageSchema: z.ZodObject<{
completeness: z.ZodType;
@@ -4200,6 +4363,62 @@ export const EvidenceCoverageSchema: z.ZodObject<{
/** Requested evidence domain for bounded coverage and omissions. */
export const EvidenceDomainSchema: z.ZodEnum<{ "anchor": "anchor"; "diagnostic": "diagnostic"; "graph": "graph"; "operational": "operational"; "source": "source"; "symbol": "symbol"; "temporal": "temporal"; "test": "test" }> = z.enum(["anchor", "diagnostic", "graph", "operational", "source", "symbol", "temporal", "test"]);
+/** Immutable evidence packet consumed by adapters and later planner work. */
+export const EvidencePacketSchema: z.ZodObject<{
+ authority: z.ZodType;
+ contributions: z.ZodType, unknown>;
+ coverage: z.ZodType;
+ evidence_authorities: z.ZodType, unknown>;
+ execution: z.ZodType;
+ omissions: z.ZodType, unknown>;
+ page: z.ZodType;
+ payload: z.ZodType;
+ scores: z.ZodType, unknown>;
+ temporal: z.ZodType;
+}, z.core.$strict> = z.object({
+ authority: z.lazy(() => AuthorityReceiptSchema),
+ contributions: z.array(z.lazy(() => RetrieverContributionSchema)),
+ coverage: z.lazy(() => EvidenceCoverageSchema),
+ evidence_authorities: z.array(z.lazy(() => EvidenceAuthoritySchema)),
+ execution: z.lazy(() => OperationReceiptSchema),
+ omissions: z.array(z.lazy(() => OmissionSchema)),
+ page: z.lazy(() => PageStateSchema),
+ payload: z.unknown(),
+ scores: z.array(z.lazy(() => EvidenceScoreSchema)),
+ temporal: z.lazy(() => TemporalStateSchema),
+}).strict();
+
+export const EvidenceScoreSchema: z.ZodObject<{
+ calibration_revision: z.ZodType;
+ calibration_valid: z.ZodType;
+ deterministic_components: z.ZodType, unknown>;
+ kind: z.ZodType;
+ score_id: z.ZodType;
+ value: z.ZodType;
+}, z.core.$strict> = z.object({
+ calibration_revision: z.union([z.lazy(() => ComponentVersionSchema), z.null()]),
+ calibration_valid: z.boolean().nullable(),
+ deterministic_components: z.array(z.string()),
+ kind: z.lazy(() => EvidenceScoreKindSchema),
+ score_id: z.string(),
+ value: z.lazy(() => EvidenceScoreValueSchema),
+}).strict();
+
+/** Evidence score semantics. Scores are metadata, never authority. */
+export const EvidenceScoreKindSchema: z.ZodEnum<{ "calibrated_interval": "calibrated_interval"; "calibrated_probability": "calibrated_probability"; "heuristic_score": "heuristic_score"; "ordinal_rank": "ordinal_rank" }> = z.enum(["calibrated_interval", "calibrated_probability", "heuristic_score", "ordinal_rank"]);
+
+export const EvidenceScoreValueSchema: z.ZodType = z.discriminatedUnion("kind", [z.object({
+ kind: z.literal("fixed_point"),
+ micros: z.number().int().min(0),
+}), z.object({
+ kind: z.literal("interval"),
+ lower_micros: z.number().int().min(0),
+ upper_micros: z.number().int().min(0),
+}), z.object({
+ kind: z.literal("ordinal"),
+ rank: z.number().int().min(0),
+})]);
+
/** Cause a work item was blocked for. */
export const ExecutionBlockedCauseV1Schema: z.ZodEnum<{ "backpressure": "backpressure"; "capability": "capability"; "ci": "ci"; "conflict": "conflict"; "dependency": "dependency"; "effect_unknown": "effect_unknown"; "lease": "lease"; "needs_input": "needs_input"; "other": "other"; "policy": "policy"; "review": "review"; "scope": "scope"; "test": "test"; "unknown": "unknown" }> = z.enum(["backpressure", "capability", "ci", "conflict", "dependency", "effect_unknown", "lease", "needs_input", "other", "policy", "review", "scope", "test", "unknown"]);
@@ -5053,6 +5272,9 @@ export const FeedbackSystemQualityReadModelV1Schema: z.ZodObject<{
/** Strongly typed canonical identity: `FileOccurrenceId`. */
export const FileOccurrenceIdSchema: z.ZodType = z.string();
+/** Application-level freshness. Missing or partial truth never becomes current. */
+export const FreshnessStateSchema: z.ZodEnum<{ "current": "current"; "stale": "stale"; "unknown": "unknown" }> = z.enum(["current", "stale", "unknown"]);
+
/** A canonical product proposal and the exact verified graph that licensed it.
`proposal` can be moved directly into a `DecideWorkProposalRequestV1`;
@@ -7273,6 +7495,76 @@ export const ObservatoryReadModelV1Schema: z.ZodObject<{
watermark: z.string(),
});
+export const OmissionSchema: z.ZodObject<{
+ count: z.ZodType;
+ domain: z.ZodType;
+ reason: z.ZodType;
+}, z.core.$strict> = z.object({
+ count: z.number().int().min(0),
+ domain: z.lazy(() => EvidenceDomainSchema),
+ reason: z.lazy(() => OmissionReasonSchema),
+}).strict();
+
+/** Safe reason why authorized requested evidence was omitted. */
+export const OmissionReasonSchema: z.ZodType<"budget" | "cancelled" | "conflict" | "failed" | "redacted" | "stale" | "timed_out" | "unavailable" | "unsupported" | "macro_body_unparsed" | "import_unmodeled" | "cursor_expired" | "cursor_foreign", unknown> = z.union([z.enum(["budget", "cancelled", "conflict", "failed", "redacted", "stale", "timed_out", "unavailable", "unsupported"]), z.literal("macro_body_unparsed"), z.literal("import_unmodeled"), z.literal("cursor_expired"), z.literal("cursor_foreign")]);
+
+/** Bounded work accounting supplied by an owning port or transaction. */
+export const OperationBudgetUsageSchema: z.ZodObject<{
+ bytes_consumed: z.ZodType;
+ elapsed_micros: z.ZodType;
+ units_consumed: z.ZodType;
+}, z.core.$strict> = z.object({
+ bytes_consumed: z.number().int().min(0),
+ elapsed_micros: z.number().int().min(0),
+ units_consumed: z.number().int().min(0),
+}).strict();
+
+/** Canonical operation evidence. An admitted failure remains represented here
+rather than being replaced by a transport exception. */
+export const OperationReceiptSchema: z.ZodObject<{
+ budget: z.ZodType;
+ cancellation: z.ZodType;
+ effective_deadline: z.ZodType;
+ ended_at: z.ZodType;
+ started_at: z.ZodType;
+ termination: z.ZodType;
+}, z.core.$strict> = z.object({
+ budget: z.lazy(() => OperationBudgetUsageSchema),
+ cancellation: z.union([z.lazy(() => CancellationObservationSchema), z.null()]),
+ effective_deadline: z.lazy(() => DeadlineSchema),
+ ended_at: z.lazy(() => UtcMicrosSchema),
+ started_at: z.lazy(() => UtcMicrosSchema),
+ termination: z.lazy(() => OperationTerminationSchema),
+}).strict();
+
+/** Terminal state after an operation has been admitted. */
+export const OperationTerminationSchema: z.ZodEnum<{ "cancelled": "cancelled"; "completed": "completed"; "effect_unknown": "effect_unknown"; "failed": "failed"; "partial": "partial"; "timed_out": "timed_out"; "unavailable": "unavailable" }> = z.enum(["cancelled", "completed", "effect_unknown", "failed", "partial", "timed_out", "unavailable"]);
+
+/** Exact continuation authority for the enclosing evidence page: an opaque
+cursor bound to the operation and request that minted it. */
+export const PageCursorSchema: z.ZodType = z.discriminatedUnion("kind", [z.object({
+ cursor: z.string(),
+ kind: z.literal("opaque"),
+}).strict()]);
+
+/** Stable page state. General cursor bytes stay opaque until authorization is
+revalidated; structural fact cursors retain their canonical ordering type. */
+export const PageStateSchema: z.ZodObject<{
+ cursor: z.ZodType;
+ expires_at: z.ZodType;
+ returned: z.ZodType;
+ sort_contract_id: z.ZodType;
+ sort_revision: z.ZodType;
+ total: z.ZodType;
+}, z.core.$strict> = z.object({
+ cursor: z.union([z.lazy(() => PageCursorSchema), z.null()]),
+ expires_at: z.union([z.lazy(() => UtcMicrosSchema), z.null()]),
+ returned: z.number().int().min(0),
+ sort_contract_id: z.lazy(() => SortContractIdSchema),
+ sort_revision: z.number().refine(Number.isInteger).min(0),
+ total: z.number().int().min(0).nullable(),
+}).strict();
+
export const PauseWorkRunCommandSchema: z.ZodObject<{
expected_authority_version: z.ZodType;
occurred_at: z.ZodType;
@@ -7334,6 +7626,19 @@ export const PerformanceComparisonReadModelV1Schema: z.ZodObject<{
workload: z.string().nullable(),
});
+/** A policy decision pinned into a receipt or provider identity. */
+export const PolicyDecisionRefSchema: z.ZodObject<{
+ decision_id: z.ZodType;
+ digest: z.ZodType;
+ evaluator_revision: z.ZodType;
+ revision: z.ZodType;
+}, z.core.$strict> = z.object({
+ decision_id: z.string(),
+ digest: z.lazy(() => ManifestDigestSchema),
+ evaluator_revision: z.lazy(() => ComponentVersionSchema),
+ revision: z.number().int().min(0),
+}).strict();
+
/** A bounded, canonical identifier owned by the policy input schema.
It represents immutable references only; it is never a path, display
@@ -7386,6 +7691,25 @@ export const PrepareWorkProductMutationRequestV1Schema: z.ZodObject<{
selection: z.lazy(() => WorkProductSelectionScopeV1Schema),
}).strict();
+/** Read-only preview of a future typed effect. */
+export const PreviewResultSchema: z.ZodObject<{
+ authority: z.ZodType;
+ effect_class: z.ZodType;
+ execution: z.ZodType;
+ expected_state: z.ZodType;
+ payload: z.ZodType;
+ preview_digest: z.ZodType;
+ preview_id: z.ZodType;
+}, z.core.$strict> = z.object({
+ authority: z.lazy(() => AuthorityReceiptSchema),
+ effect_class: z.lazy(() => EffectClassSchema),
+ execution: z.lazy(() => OperationReceiptSchema),
+ expected_state: z.lazy(() => ManifestDigestSchema),
+ payload: z.unknown(),
+ preview_digest: z.lazy(() => ManifestDigestSchema),
+ preview_id: z.string(),
+}).strict();
+
/** Whether the served code generation is known current at serve time. */
export const PrimitiveFreshnessStateV1Schema: z.ZodEnum<{ "fresh": "fresh"; "possibly_stale": "possibly_stale" }> = z.enum(["fresh", "possibly_stale"]);
@@ -7591,6 +7915,14 @@ export const ProjectSettingsPatchSchema: z.ZodObject<{
track_call_sites: z.boolean().nullable().optional(),
}).strict();
+export const ProjectSettingsPatchResponseV1Schema: z.ZodObject<{
+ application_outcome: z.ZodType;
+ current: z.ZodType, unknown>;
+}> = z.object({
+ application_outcome: z.union([z.lazy(() => ApplicationOutcomeSchema), z.null()]),
+ current: z.lazy(() => DashboardEnvelopeV1Schema(z.lazy(() => SettingsPayloadV1Schema))),
+});
+
export const ProjectSettingsPayloadV1Schema: z.ZodObject<{
config: z.ZodType;
configuration_revision_id: z.ZodType;
@@ -7761,6 +8093,9 @@ export const PublicCodeProjectSchema: z.ZodObject<{
export const QuantityEvidenceClassV1Schema: z.ZodEnum<{ "estimated": "estimated"; "locally_measured": "locally_measured"; "owner_receipt": "owner_receipt"; "unknown": "unknown" }> = z.enum(["estimated", "locally_measured", "owner_receipt", "unknown"]);
+/** Reconciliation state retained after an admitted effect. */
+export const ReconciliationStateSchema: z.ZodEnum<{ "failed": "failed"; "pending": "pending"; "reconciled": "reconciled" }> = z.enum(["failed", "pending", "reconciled"]);
+
export const RecordWorkHandoffRequestV1Schema: z.ZodObject<{
handoff: z.ZodType;
mutation: z.ZodType;
@@ -8041,6 +8376,41 @@ export const ResumeWorkRunCommandSchema: z.ZodObject<{
/** Strongly typed canonical identity: `RetrievalAnchorId`. */
export const RetrievalAnchorIdSchema: z.ZodType = z.string();
+/** One source-owned contribution to the packet fold. */
+export const RetrieverContributionSchema: z.ZodObject<{
+ contract: z.ZodType;
+ coverage: z.ZodType;
+ domain: z.ZodType;
+ elapsed_budget_class: z.ZodType;
+ evidence_authorities: z.ZodType, unknown>;
+ omitted_count: z.ZodType;
+ producer_revision: z.ZodType;
+ provenance_anchors: z.ZodType, unknown>;
+ retriever_id: z.ZodType;
+ returned_count: z.ZodType;
+ score_ids: z.ZodType, unknown>;
+ state: z.ZodType;
+}, z.core.$strict> = z.object({
+ contract: z.lazy(() => ResultContractRefSchema),
+ coverage: z.lazy(() => EvidenceCoverageSchema),
+ domain: z.lazy(() => EvidenceDomainSchema),
+ elapsed_budget_class: z.lazy(() => BudgetClassSchema),
+ evidence_authorities: z.array(z.string()),
+ omitted_count: z.number().int().min(0),
+ producer_revision: z.lazy(() => ComponentVersionSchema),
+ provenance_anchors: z.array(z.lazy(() => RetrievalAnchorIdSchema)),
+ retriever_id: z.lazy(() => RetrieverIdSchema),
+ returned_count: z.number().int().min(0),
+ score_ids: z.array(z.string()),
+ state: z.lazy(() => RetrieverContributionStateSchema),
+}).strict();
+
+/** Terminal state reported by one retriever contribution. */
+export const RetrieverContributionStateSchema: z.ZodEnum<{ "cancelled": "cancelled"; "completed": "completed"; "failed": "failed"; "partial": "partial"; "stale": "stale"; "timed_out": "timed_out"; "unavailable": "unavailable"; "unsupported": "unsupported" }> = z.enum(["cancelled", "completed", "failed", "partial", "stale", "timed_out", "unavailable", "unsupported"]);
+
+/** Stable, canonical catalog identity for `RetrieverId`. */
+export const RetrieverIdSchema: z.ZodType = z.string();
+
/** Safe adapter-independent retry instruction. Adapters preserve it verbatim. */
export const RetryDirectiveSchema: z.ZodEnum<{ "after_delay": "after_delay"; "after_reconcile": "after_reconcile"; "after_revalidate": "after_revalidate"; "never": "never"; "same_request": "same_request" }> = z.enum(["after_delay", "after_reconcile", "after_revalidate", "never", "same_request"]);
@@ -8761,6 +9131,15 @@ export const SettingsPayloadV1Schema: z.ZodObject<{
version: z.lazy(() => VersionSettingsPayloadV1Schema),
});
+/** One field-level settings validation failure. */
+export const SettingsValidationIssueV1Schema: z.ZodObject<{
+ field: z.ZodType;
+ message: z.ZodType;
+}> = z.object({
+ field: z.string(),
+ message: z.string(),
+});
+
/** Logical and verified physical identity of the shared Profile shard.
Brain and profile IDs select the shard; the verified store locator binds its
@@ -8890,6 +9269,9 @@ exists, so it is never a failure. Snapshots written before that
distinction stored `unknown` as `ignored`. */
export const SkillOutcomeVerdictSchema: z.ZodEnum<{ "adopted": "adopted"; "too_early": "too_early"; "unknown": "unknown" }> = z.enum(["adopted", "too_early", "unknown"]);
+/** Stable, canonical catalog identity for `SortContractId`. */
+export const SortContractIdSchema: z.ZodType = z.string();
+
/** Byte range inside one sanitized source file. Mutable line numbers are
never part of identity. */
export const SourceSpanSchema: z.ZodObject<{
@@ -9374,6 +9756,25 @@ export const TemporalModeV1Schema: z.ZodType = z.discri
kind: z.literal("forensic"),
}).strict()]);
+/** Temporal provenance for a packet. */
+export const TemporalStateSchema: z.ZodObject<{
+ code_graph_freshness: z.ZodOptional>;
+ freshness: z.ZodType;
+ requested_at: z.ZodType;
+ requested_mode: z.ZodType;
+ resolved_at: z.ZodType;
+ source_generation: z.ZodType;
+ watermark_digest: z.ZodType;
+}, z.core.$strict> = z.object({
+ code_graph_freshness: z.union([z.lazy(() => CodeGraphReadFreshnessV1Schema), z.null()]).optional(),
+ freshness: z.lazy(() => FreshnessStateSchema),
+ requested_at: z.lazy(() => UtcMicrosSchema),
+ requested_mode: z.lazy(() => TemporalModeV1Schema),
+ resolved_at: z.lazy(() => UtcMicrosSchema),
+ source_generation: z.union([z.lazy(() => CodeGenerationIdSchema), z.null()]),
+ watermark_digest: z.union([z.lazy(() => ManifestDigestSchema), z.null()]),
+}).strict();
+
export const TestMapMeasurementV1Schema: z.ZodObject<{
algorithm: z.ZodType;
applicable: z.ZodType;
diff --git a/dashboard/src/contracts/types.ts b/dashboard/src/contracts/types.ts
index 7eceeb6733..5f38d42963 100644
--- a/dashboard/src/contracts/types.ts
+++ b/dashboard/src/contracts/types.ts
@@ -320,7 +320,25 @@ export type AnalyticsUsageSummaryV1 = {
export type ApplicationExecutionFailureClassV1 = "denied" | "malformed_output" | "permanent";
-export type ApplicationProblemDetailV1 =
+export type ApplicationOutcome =
+ | {
+ outcome: "effect";
+ value: EffectResult;
+ }
+ | {
+ outcome: "evidence";
+ value: EvidencePacket;
+ }
+ | {
+ outcome: "preview";
+ value: PreviewResult;
+ }
+ | {
+ outcome: "result";
+ value: unknown;
+ };
+
+export type ApplicationProblemDetailV1 =
| {
kind: "code_graph_rewarming";
retry_after_millis: number;
@@ -420,6 +438,16 @@ export type ApplyWorkRelationReplanRequestV1 = {
export type AttemptId = string;
+export type AuthorityReceipt = {
+ authorized_scope_digest: ManifestDigest;
+ disclosure: DisclosureClass;
+ grant_digest: ManifestDigest;
+ grant_id: string;
+ grant_revision: number;
+ policy: PolicyDecisionRef;
+ revalidated_at: UtcMicros;
+};
+
export type AuthorizedRoot = {
locator: RegisteredRootLocatorV1 | null;
scope: ResolvedScope;
@@ -461,7 +489,7 @@ export type AutomaticFactReceiptsPayloadV1 = {
export type AutomaticFactState = "applied" | "quarantined";
-export type AutomaticWorktreeGcV1 =
+export type AutomaticWorktreeGcV1 =
| {
kind: "disabled";
}
@@ -471,7 +499,7 @@ export type AutomaticWorktreeGcV1 =
minimum_idle_seconds: number;
};
-export type AutomationCommittedReceiptV1 =
+export type AutomationCommittedReceiptV1 =
| {
kind: "automatic_fact";
receipt: MemoryAutomationFactReceiptV1;
@@ -661,7 +689,7 @@ export type AutomationRunSummaryV1 = {
skipped_count: number;
};
-export type AutomationRunTerminalV1 =
+export type AutomationRunTerminalV1 =
| {
status: "completed";
summary: AutomationRunSummaryV1;
@@ -715,7 +743,7 @@ export type AutomationTrigger = "application" | "dashboard" | "host_receipt" | "
export type BrainId = string;
-export type BranchCollisionPolicyV1 =
+export type BranchCollisionPolicyV1 =
| {
kind: "append_monotonic_ordinal";
maximum_attempts: number;
@@ -724,7 +752,7 @@ export type BranchCollisionPolicyV1 =
kind: "reject";
};
-export type BranchNameComponentV1 =
+export type BranchNameComponentV1 =
| {
kind: "monotonic_collision_ordinal";
}
@@ -755,6 +783,8 @@ export type BranchTopologyPolicyV1 = {
allowed: Array;
};
+export type BudgetClass = "approaching_limit" | "exhausted" | "within_budget";
+
export type CallChainMeasurementV1 = {
complete: boolean;
directed: boolean;
@@ -773,6 +803,11 @@ export type CallChainStepV1 = {
node: NodeRefV1;
};
+export type CancellationObservation = {
+ observed_at: UtcMicros;
+ stage: CancellationStage;
+};
+
export type CancellationStage = "after_commit" | "before_admission" | "before_effect" | "before_read" | "during_read" | "effect_in_flight" | "reconciling";
export type CancelWorkAttemptCommand = {
@@ -828,7 +863,7 @@ export type CodeCloneIndexResourcesV1 = {
stale_invalidations: number | null;
};
-export type CodeCloneIndexStatusV1 =
+export type CodeCloneIndexStatusV1 =
| {
observation: CodeCloneIndexObservationV1;
omission_reasons: Array;
@@ -850,7 +885,17 @@ export type CodeCloneIndexStatusV1 =
export type CodeGenerationId = string;
-export type CodeGraphServingReadinessV1 =
+export type CodeGraphReadFreshnessV1 =
+ | {
+ state: "current";
+ }
+ | {
+ rebuild_in_flight: boolean;
+ sealed_at: UtcMicros;
+ state: "last_complete_stale";
+ };
+
+export type CodeGraphServingReadinessV1 =
| {
state: "pending";
}
@@ -955,7 +1000,7 @@ export type CodeIndexRouteRefusalV1 = {
remediation: string;
};
-export type CodeIndexSourceOmissionReasonV1 =
+export type CodeIndexSourceOmissionReasonV1 =
| {
detail: string;
kind: "privacy_withheld";
@@ -968,7 +1013,7 @@ export type CodeIndexStalenessStateV1 = "fresh" | "indexing" | "parked" | "refre
export type CodeIndexWorkerLimitingReasonV1 = "automatic_all_cores" | "automatic_half_cores" | "configured_exact" | "environment_override" | "resident_memory";
-export type CodeIndexWorkerSelectionV1 =
+export type CodeIndexWorkerSelectionV1 =
| {
mode: "automatic";
}
@@ -1023,6 +1068,22 @@ export type ComplexityAnalysisV1 = "complete" | "traversal_budget_exhausted";
export type ComponentVersion = string;
+export type ConfigurationRefusalV1 =
+ | {
+ code: "application_contract_violation";
+ detail: string;
+ }
+ | {
+ code: "configuration_authority_unavailable";
+ detail: string;
+ }
+ | {
+ actual_revision_id: string;
+ code: "configuration_revision_conflict";
+ detail: string;
+ expected_revision_id: string;
+ };
+
export type ConfigurationRevisionId = string;
export type ConfigurationSnapshotId = string;
@@ -1096,7 +1157,7 @@ export type CrossMergePolicyV1 = {
default_mode: CrossMergeModeV1;
};
-export type DaemonServiceUnitObservationV1 =
+export type DaemonServiceUnitObservationV1 =
| {
path: string;
serves: string;
@@ -1110,7 +1171,7 @@ export type DaemonServiceUnitObservationV1 =
state: "unobservable";
};
-export type DashboardAuthorizationV1 =
+export type DashboardAuthorizationV1 =
| {
outcome: "authorized";
}
@@ -1124,6 +1185,11 @@ export type DashboardAuthorizationV1 =
outcome: "unauthorized";
};
+export type DashboardConfigurationRefusalV1 = {
+ detail: string;
+ validation_errors: Array;
+} | ConfigurationRefusalV1 | ApplicationProblemEnvelope;
+
export type DashboardCoverageCompletenessV1 = "complete" | "partial" | "unknown" | "unsupported";
export type DashboardCoverageV1 = {
@@ -1193,6 +1259,10 @@ export type DashboardWatermarkV1 = {
watermark: string;
};
+export type Deadline = {
+ expires_at: UtcMicros;
+};
+
export type DecideWorkProposalRequestV1 = {
disposition: WorkProposalDispositionV12;
mutation: WorkProductMutationIdentityV1;
@@ -1233,7 +1303,7 @@ export type DeliveryAgentUsageV1 = {
usage_coverage: ProviderUsageCoverageV1;
};
-export type DeliveryAttentionEvidenceV1 =
+export type DeliveryAttentionEvidenceV1 =
| {
failure_anchor: string;
kind: "ci_failure";
@@ -1358,7 +1428,7 @@ export type DeliveryGenerationFreshnessV1 = {
indexed_commit: string;
};
-export type DeliveryGitHeadV1 =
+export type DeliveryGitHeadV1 =
| {
branch: string;
commit: string;
@@ -1449,7 +1519,7 @@ export type DeliveryInboxV1 = {
registry_state: DeliveryRegistryStateV1;
};
-export type DeliveryMembershipBasisV1 =
+export type DeliveryMembershipBasisV1 =
| {
branch_ref: string;
head_commit_id: string;
@@ -1492,7 +1562,7 @@ export type DeliveryOverviewV1 = {
review_comments: DeliveryProjectionV14;
};
-export type DeliveryProjectionV1 =
+export type DeliveryProjectionV1 =
| {
state: "denied";
value: DeliveryGitStatusV1 | null;
@@ -1535,7 +1605,7 @@ export type DeliveryProjectionV1 =
value: DeliveryGitStatusV1 | null;
};
-export type DeliveryProjectionV12 =
+export type DeliveryProjectionV12 =
| {
state: "denied";
value: DeliveryCommitTimelineV1 | null;
@@ -1578,7 +1648,7 @@ export type DeliveryProjectionV12 =
value: DeliveryCommitTimelineV1 | null;
};
-export type DeliveryProjectionV13 =
+export type DeliveryProjectionV13 =
| {
state: "denied";
value: DeliveryPullRequestTimelineV1 | null;
@@ -1621,7 +1691,7 @@ export type DeliveryProjectionV13 =
value: DeliveryPullRequestTimelineV1 | null;
};
-export type DeliveryProjectionV14 =
+export type DeliveryProjectionV14 =
| {
state: "denied";
value: DeliveryReviewTimelineV1 | null;
@@ -1664,7 +1734,7 @@ export type DeliveryProjectionV14 =
value: DeliveryReviewTimelineV1 | null;
};
-export type DeliveryProjectionV15 =
+export type DeliveryProjectionV15 =
| {
state: "denied";
value: DeliveryCiTimelineV1 | null;
@@ -1707,7 +1777,7 @@ export type DeliveryProjectionV15 =
value: DeliveryCiTimelineV1 | null;
};
-export type DeliveryProjectionV16 =
+export type DeliveryProjectionV16 =
| {
state: "denied";
value: DeliveryFailureLocalizationTimelineV1 | null;
@@ -1750,7 +1820,7 @@ export type DeliveryProjectionV16 =
value: DeliveryFailureLocalizationTimelineV1 | null;
};
-export type DeliveryProjectionV17 =
+export type DeliveryProjectionV17 =
| {
state: "denied";
value: DeliveryReleaseTimelineV1 | null;
@@ -1793,7 +1863,7 @@ export type DeliveryProjectionV17 =
value: DeliveryReleaseTimelineV1 | null;
};
-export type DeliveryProjectionV18 =
+export type DeliveryProjectionV18 =
| {
state: "denied";
value: DeliveryGenerationFreshnessV1 | null;
@@ -1836,7 +1906,7 @@ export type DeliveryProjectionV18 =
value: DeliveryGenerationFreshnessV1 | null;
};
-export type DeliveryProjectionV19 =
+export type DeliveryProjectionV19 =
| {
state: "denied";
value: DeliveryAgentUsageV1 | null;
@@ -2024,6 +2094,8 @@ export type DiagnosticsSearchedTsconfigV1 = {
present: boolean;
};
+export type DisclosureClass = "evidence" | "metadata" | "sensitive";
+
export type DoctorCoverageCompletenessV1 = "complete" | "partial" | "unknown";
export type DoctorCoverageStatementV1 = {
@@ -2040,7 +2112,7 @@ export type DoctorEvidenceRefV1 = {
export type DoctorEvidenceStateV1 = "unsupported" | "absent" | "stale" | "degraded" | "partial" | "unknown" | "denied" | "healthy_complete_coverage";
-export type DoctorFamilyConsultationV1 =
+export type DoctorFamilyConsultationV1 =
| {
status: "consulted";
}
@@ -2112,6 +2184,18 @@ export type EffectReceipt = {
scope: ResolvedScope;
};
+export type EffectResult = {
+ authority: AuthorityReceipt;
+ effect_class: EffectClass;
+ effect_id: string;
+ execution: OperationReceipt;
+ expected_state: ManifestDigest;
+ idempotency_key: string;
+ payload: unknown;
+ receipt: EffectReceipt;
+ reconciliation: ReconciliationState;
+};
+
export type EffectTermination = "cancelled" | "completed" | "effect_unknown" | "failed" | "partial" | "timed_out";
export type EnvironmentSettingsPayloadV1 = {
@@ -2128,6 +2212,15 @@ export type EnvironmentVariableV1 = {
value: string | null;
};
+export type EvidenceAuthority = {
+ evidence_id: string;
+ horizon: UtcMicros | null;
+ producer: string;
+ revision: ComponentVersion;
+ scope: ResolvedScope;
+ source_kind: string;
+};
+
export type EvidenceCoverage = {
completeness: CoverageCompleteness;
domains: Array;
@@ -2139,6 +2232,45 @@ export type EvidenceCoverage = {
export type EvidenceDomain = "anchor" | "diagnostic" | "graph" | "operational" | "source" | "symbol" | "temporal" | "test";
+export type EvidencePacket = {
+ authority: AuthorityReceipt;
+ contributions: Array;
+ coverage: EvidenceCoverage;
+ evidence_authorities: Array;
+ execution: OperationReceipt;
+ omissions: Array;
+ page: PageState;
+ payload: unknown;
+ scores: Array;
+ temporal: TemporalState;
+};
+
+export type EvidenceScore = {
+ calibration_revision: ComponentVersion | null;
+ calibration_valid: boolean | null;
+ deterministic_components: Array;
+ kind: EvidenceScoreKind;
+ score_id: string;
+ value: EvidenceScoreValue;
+};
+
+export type EvidenceScoreKind = "calibrated_interval" | "calibrated_probability" | "heuristic_score" | "ordinal_rank";
+
+export type EvidenceScoreValue =
+ | {
+ kind: "fixed_point";
+ micros: number;
+ }
+ | {
+ kind: "interval";
+ lower_micros: number;
+ upper_micros: number;
+ }
+ | {
+ kind: "ordinal";
+ rank: number;
+ };
+
export type ExecutionBlockedCauseV1 = "backpressure" | "capability" | "ci" | "conflict" | "dependency" | "effect_unknown" | "lease" | "needs_input" | "other" | "policy" | "review" | "scope" | "test" | "unknown";
export type ExecutionConcurrencyPhaseV1 = "accepted" | "active" | "admitted" | "requested" | "useful";
@@ -2189,7 +2321,7 @@ export type ExecutionStackDriftKindV1 = "base_advanced" | "head_advanced" | "mer
export type ExecutionSurfaceFamilyV1 = "cli" | "dashboard" | "hook" | "lsp" | "mcp" | "other";
-export type ExecutionTopologyDimensionV1 =
+export type ExecutionTopologyDimensionV1 =
| {
dimension: "blocked_cause";
value: ExecutionBlockedCauseV1;
@@ -2306,7 +2438,7 @@ export type ExecutionTopologyMetricsV1 = {
watermark: string;
};
-export type ExecutionTopologyViewV1 =
+export type ExecutionTopologyViewV1 =
| {
state: "absent";
}
@@ -2421,7 +2553,7 @@ export type FactCategoryV1 = "code_area" | "decision" | "general" | "project" |
export type FactCommitDispositionV1 = "committed" | "idempotent_replay";
-export type FactCommitOwnerV1 =
+export type FactCommitOwnerV1 =
| {
kind: "profile";
}
@@ -2482,7 +2614,7 @@ export type FactOutcomeRecord = {
export type FactOutcomeVerdict = "deleted" | "never_recalled" | "quarantined" | "recalled" | "recalled_and_helpful" | "unavailable";
-export type FactSearchGraphCoverageV1 =
+export type FactSearchGraphCoverageV1 =
| {
expanded_fact_count: number;
kind: "complete";
@@ -2613,7 +2745,7 @@ export type FeedbackProximityReadRequestV1 = {
observed_at: UtcMicros;
};
-export type FeedbackProximityReadResultV1 =
+export type FeedbackProximityReadResultV1 =
| {
page: FeedbackProximityReadPageV1;
state: "complete";
@@ -2641,7 +2773,7 @@ export type FeedbackProximityReadResultV1 =
state: "unavailable";
};
-export type FeedbackProximityRelationV1 =
+export type FeedbackProximityRelationV1 =
| {
relation_kind: "code_neighborhood_candidate";
warning_class: ProximityWarningClassV1;
@@ -2702,6 +2834,8 @@ export type FeedbackSystemQualityReadModelV1 = {
export type FileOccurrenceId = string;
+export type FreshnessState = "current" | "stale" | "unknown";
+
export type GeneratedWorkProposal = {
calibration: WorkCalibrationEvidenceV1;
decision: WorkProposalDecisionV1;
@@ -2855,7 +2989,7 @@ export type GraphTotalsV1 = {
export type HandoffOpenKindV1 = "investigation" | "task";
-export type HandoffOpenTargetV1 =
+export type HandoffOpenTargetV1 =
| {
finding_id: FeedbackFindingId;
kind: "investigation";
@@ -2892,7 +3026,7 @@ export type IntentWindowSettingsV1 = {
max_user_turns: number;
};
-export type JobDelivery =
+export type JobDelivery =
| {
mode: "file";
path?: string | null | undefined;
@@ -3174,7 +3308,7 @@ export type LoomEditedFileV1 = {
session_id: string;
};
-export type LoomEventV1 =
+export type LoomEventV1 =
| {
kind: "pull_request";
label?: string | null | undefined;
@@ -3346,7 +3480,7 @@ export type MemoryAutomationCurationMergeV1 = {
winner_fact_id: FactId;
};
-export type MemoryAutomationCurationOperationEffectV1 =
+export type MemoryAutomationCurationOperationEffectV1 =
| {
closest_fact_id: FactId | null;
commit: FactCommitReceiptV1 | null;
@@ -3439,7 +3573,7 @@ export type MemoryAutomationFactDedupeValidationV1 = {
export type MemoryAutomationFactDispositionV1 = "already_applied" | "applied" | "quarantined";
-export type MemoryAutomationFactEffectV1 =
+export type MemoryAutomationFactEffectV1 =
| {
assertion_id: FactAssertionId;
event_id: FactEventId;
@@ -3602,7 +3736,7 @@ export type MemoryGraphEdgeV1 = {
target: string;
};
-export type MemoryGraphNodeV1 =
+export type MemoryGraphNodeV1 =
| {
assertion_id: FactAssertionId;
fact_id: FactId;
@@ -3909,7 +4043,7 @@ export type MetricValueV1 = {
value: number | null;
};
-export type MultiRootCapabilityV1 =
+export type MultiRootCapabilityV1 =
| {
revision: ScopeSetRevision;
root_count: number;
@@ -3941,7 +4075,7 @@ export type MultiRootExecuteRequestV1 = {
scope_set_revision: ScopeSetRevision;
};
-export type MultiRootOperationV1 =
+export type MultiRootOperationV1 =
| {
kind: "feedback";
request: unknown;
@@ -4031,6 +4165,46 @@ export type ObservatoryReadModelV1 = {
watermark: string;
};
+export type Omission = {
+ count: number;
+ domain: EvidenceDomain;
+ reason: OmissionReason;
+};
+
+export type OmissionReason = "budget" | "cancelled" | "conflict" | "failed" | "redacted" | "stale" | "timed_out" | "unavailable" | "unsupported" | "macro_body_unparsed" | "import_unmodeled" | "cursor_expired" | "cursor_foreign";
+
+export type OperationBudgetUsage = {
+ bytes_consumed: number;
+ elapsed_micros: number;
+ units_consumed: number;
+};
+
+export type OperationReceipt = {
+ budget: OperationBudgetUsage;
+ cancellation: CancellationObservation | null;
+ effective_deadline: Deadline;
+ ended_at: UtcMicros;
+ started_at: UtcMicros;
+ termination: OperationTermination;
+};
+
+export type OperationTermination = "cancelled" | "completed" | "effect_unknown" | "failed" | "partial" | "timed_out" | "unavailable";
+
+export type PageCursor =
+ | {
+ cursor: string;
+ kind: "opaque";
+ };
+
+export type PageState = {
+ cursor: PageCursor | null;
+ expires_at: UtcMicros | null;
+ returned: number;
+ sort_contract_id: SortContractId;
+ sort_revision: number;
+ total: number | null;
+};
+
export type PauseWorkRunCommand = {
expected_authority_version: number | null;
occurred_at: UtcMicros;
@@ -4066,6 +4240,13 @@ export type PerformanceComparisonReadModelV1 = {
workload: string | null;
};
+export type PolicyDecisionRef = {
+ decision_id: string;
+ digest: ManifestDigest;
+ evaluator_revision: ComponentVersion;
+ revision: number;
+};
+
export type PolicyIdentifierV1 = string;
export type PrAutoTrackEntryV1 = {
@@ -4093,6 +4274,16 @@ export type PrepareWorkProductMutationRequestV1 = {
selection: WorkProductSelectionScopeV1;
};
+export type PreviewResult = {
+ authority: AuthorityReceipt;
+ effect_class: EffectClass;
+ execution: OperationReceipt;
+ expected_state: ManifestDigest;
+ payload: unknown;
+ preview_digest: ManifestDigest;
+ preview_id: string;
+};
+
export type PrimitiveFreshnessStateV1 = "fresh" | "possibly_stale";
export type PrimitiveIndexingStateV1 = {
@@ -4202,6 +4393,11 @@ export type ProjectSettingsPatch = {
track_call_sites?: boolean | null | undefined;
};
+export type ProjectSettingsPatchResponseV1 = {
+ application_outcome: ApplicationOutcome | null;
+ current: DashboardEnvelopeV1;
+};
+
export type ProjectSettingsPayloadV1 = {
config: ProjectEditableSettingsV1;
configuration_revision_id: string;
@@ -4230,7 +4426,7 @@ export type ProtectedRefRuleV1 = {
selector: ProtectedRefSelectorV1;
};
-export type ProtectedRefSelectorV1 =
+export type ProtectedRefSelectorV1 =
| {
kind: "exact";
value: CanonicalGitRefNameV1;
@@ -4304,6 +4500,8 @@ export type PublicCodeProject = {
export type QuantityEvidenceClassV1 = "estimated" | "locally_measured" | "owner_receipt" | "unknown";
+export type ReconciliationState = "failed" | "pending" | "reconciled";
+
export type RecordWorkHandoffRequestV1 = {
handoff: WorkHandoffV1;
mutation: WorkProductMutationIdentityV1;
@@ -4359,7 +4557,7 @@ export type ReleaseWorkPlacementCommand = {
export type RemoteAuthorityMissingReasonV1 = "authority_authentication_failed" | "authority_unreachable" | "caller_authentication_failed" | "enrollment_expired" | "enrollment_revoked" | "fence_unverified" | "insufficient_capability" | "placement_unknown" | "protocol_incompatible" | "registry_unavailable" | "scope_mismatch" | "writer_authority_unpublished";
-export type RemoteAuthoritySummaryV1 =
+export type RemoteAuthoritySummaryV1 =
| {
fence: RemoteFenceSummaryV1;
state: "available";
@@ -4385,7 +4583,7 @@ export type RemoteFenceSummaryV1 = {
export type RemoteListenerKindV1 = "degraded" | "disabled" | "serving";
-export type RemoteOperationalStatusPayloadV1 =
+export type RemoteOperationalStatusPayloadV1 =
| {
authority: RemoteAuthoritySummaryV1;
coverage: DoctorCoverageCompletenessV1;
@@ -4417,7 +4615,7 @@ export type RemoteSpoolSummaryV1 = {
export type RepositoryId = string;
-export type RepositoryPlacementScopeV1 =
+export type RepositoryPlacementScopeV1 =
| {
kind: "all_authorized";
}
@@ -4480,6 +4678,25 @@ export type ResumeWorkRunCommand = {
export type RetrievalAnchorId = string;
+export type RetrieverContribution = {
+ contract: ResultContractRef;
+ coverage: EvidenceCoverage;
+ domain: EvidenceDomain;
+ elapsed_budget_class: BudgetClass;
+ evidence_authorities: Array;
+ omitted_count: number;
+ producer_revision: ComponentVersion;
+ provenance_anchors: Array;
+ retriever_id: RetrieverId;
+ returned_count: number;
+ score_ids: Array;
+ state: RetrieverContributionState;
+};
+
+export type RetrieverContributionState = "cancelled" | "completed" | "failed" | "partial" | "stale" | "timed_out" | "unavailable" | "unsupported";
+
+export type RetrieverId = string;
+
export type RetryDirective = "after_delay" | "after_reconcile" | "after_revalidate" | "never" | "same_request";
export type RetryScope = "fresh_request" | "same_operation" | "same_request";
@@ -4491,7 +4708,7 @@ export type RetryWorkAttemptCommandV1 = {
original_attempt: WorkAttemptIdentityV1;
};
-export type ReviewRequirementV1 =
+export type ReviewRequirementV1 =
| {
kind: "code_owner_and_independent_review";
}
@@ -4777,7 +4994,7 @@ export type SchemaConvergenceFindingV1 = {
store: string;
};
-export type SchemaConvergenceProgressV1 =
+export type SchemaConvergenceProgressV1 =
| {
done: number;
remaining: number;
@@ -4795,7 +5012,7 @@ export type SchemaConvergenceStateV1 = "completed" | "degraded" | "pending_schem
export type SchemaId = string;
-export type ScopeOutcome_for_Array_of_AnyValue =
+export type ScopeOutcome_for_Array_of_AnyValue =
| {
outcome: "denied";
}
@@ -4817,7 +5034,7 @@ export type ScopeOutcome_for_Array_of_AnyValue =
};
};
-export type ScopeOutcome_for_Nullable_RootGenerationV1 =
+export type ScopeOutcome_for_Nullable_RootGenerationV1 =
| {
outcome: "denied";
}
@@ -4839,7 +5056,7 @@ export type ScopeOutcome_for_Nullable_RootGenerationV1 =
};
};
-export type ScopeOutcome_for_Nullable_string =
+export type ScopeOutcome_for_Nullable_string =
| {
outcome: "denied";
}
@@ -4883,7 +5100,7 @@ export type SessionRetrievalBudgetAccountingV1 = {
observed: SessionRetrievalBudgetObservationV1;
};
-export type SessionRetrievalBudgetObservationV1 =
+export type SessionRetrievalBudgetObservationV1 =
| {
observation: "consumed_with_more_available";
units: number;
@@ -4895,7 +5112,7 @@ export type SessionRetrievalBudgetObservationV1 =
export type SessionRetrievalBudgetStageV1 = "candidate_read_exhausted" | "context_bytes" | "context_tokens" | "cursor_manifest_limit" | "estimator_version_mismatch" | "execution_work_exhausted" | "hydration_bytes" | "kernel_result_limit" | "participant_manifest_canonical_bytes" | "participant_manifest_participants" | "record_read_exhausted" | "request_candidate_bytes" | "request_context_bytes" | "request_hydration_bytes" | "request_hydration_limit" | "request_record_bytes" | "request_result_limit";
-export type SessionRetrievalStructuralRefusalV1 =
+export type SessionRetrievalStructuralRefusalV1 =
| {
accounting: SessionRetrievalBudgetAccountingV1 | null;
refusal: "budget_exhausted";
@@ -4925,6 +5142,11 @@ export type SettingsPayloadV1 = {
version: VersionSettingsPayloadV1;
};
+export type SettingsValidationIssueV1 = {
+ field: string;
+ message: string;
+};
+
export type SharedProfileStoreLocatorV1 = {
brain_id: BrainId;
profile_id: UserProfileId;
@@ -4940,7 +5162,7 @@ export type SignificantTableGrowthSampleV1 = {
table: string;
};
-export type SimilarCoverageV1 =
+export type SimilarCoverageV1 =
| {
status: "complete";
}
@@ -5006,6 +5228,8 @@ export type SkillOutcomeRecord = {
export type SkillOutcomeVerdict = "adopted" | "too_early" | "unknown";
+export type SortContractId = string;
+
export type SourceSpan = {
end_byte: number;
start_byte: number;
@@ -5059,7 +5283,7 @@ export type StorageTelemetryPayloadV1 = {
table_growth_threshold: TableGrowthThresholdV1;
};
-export type StorageTelemetryReadV1 =
+export type StorageTelemetryReadV1 =
| {
kind: "denied";
store: StoreKeyV1;
@@ -5083,7 +5307,7 @@ export type StorageTelemetryReadV1 =
store: StoreKeyV1;
};
-export type StoreBudgetDimensionV1 =
+export type StoreBudgetDimensionV1 =
| {
evaluation: StoreBudgetEvaluationV1;
reason: string;
@@ -5100,7 +5324,7 @@ export type StoreBudgetDimensionV1 =
state: "unset";
};
-export type StoreBudgetEvaluationV1 =
+export type StoreBudgetEvaluationV1 =
| {
observed: StorageByteSizeV1;
overage: StorageByteSizeV1;
@@ -5113,7 +5337,7 @@ export type StoreBudgetEvaluationV1 =
state: "within_budget";
};
-export type StoreGrowthDimensionV1 =
+export type StoreGrowthDimensionV1 =
| {
reason: string;
state: "unknown";
@@ -5184,7 +5408,7 @@ export type StrataScanV1 = {
files_examined: number;
};
-export type StructureReadV1 =
+export type StructureReadV1 =
| {
code: string;
detail: string;
@@ -5201,7 +5425,7 @@ export type StructureReadV1 =
status: "unmeasured";
};
-export type StructureReadV12 =
+export type StructureReadV12 =
| {
code: string;
detail: string;
@@ -5218,7 +5442,7 @@ export type StructureReadV12 =
status: "unmeasured";
};
-export type StructureReadV13 =
+export type StructureReadV13 =
| {
code: string;
detail: string;
@@ -5235,7 +5459,7 @@ export type StructureReadV13 =
status: "unmeasured";
};
-export type StructureReadV14 =
+export type StructureReadV14 =
| {
code: string;
detail: string;
@@ -5252,7 +5476,7 @@ export type StructureReadV14 =
status: "unmeasured";
};
-export type StructureReadV15 =
+export type StructureReadV15 =
| {
code: string;
detail: string;
@@ -5281,7 +5505,7 @@ export type SyncSettingsV1 = {
auto_track_pr_poll_secs: number;
};
-export type TableGrowthDimensionV1 =
+export type TableGrowthDimensionV1 =
| {
coverage: DashboardCoverageV1;
observed_at: number;
@@ -5312,7 +5536,7 @@ export type TableGrowthDimensionV1 =
state: "unsupported";
};
-export type TableGrowthOmissionV1 =
+export type TableGrowthOmissionV1 =
| {
current_bytes: number;
kind: "baseline_pending";
@@ -5358,7 +5582,7 @@ export type TelemetrySettingsV1 = {
timings: boolean;
};
-export type TemporalModeV1 =
+export type TemporalModeV1 =
| {
cutoff: UtcMicros;
kind: "as_of";
@@ -5373,6 +5597,16 @@ export type TemporalModeV1 =
kind: "forensic";
};
+export type TemporalState = {
+ code_graph_freshness?: CodeGraphReadFreshnessV1 | null | undefined;
+ freshness: FreshnessState;
+ requested_at: UtcMicros;
+ requested_mode: TemporalModeV1;
+ resolved_at: UtcMicros;
+ source_generation: CodeGenerationId | null;
+ watermark_digest: ManifestDigest | null;
+};
+
export type TestMapMeasurementV1 = {
algorithm: string;
applicable: boolean;
@@ -5505,7 +5739,7 @@ export type WorkArtifactHydrationRequestV1 = {
page_size: number;
};
-export type WorkArtifactHydrationV1 =
+export type WorkArtifactHydrationV1 =
| {
state: "absent";
}
@@ -5542,7 +5776,7 @@ export type WorkAttemptEvidenceRecordV1 = {
stdout: WorkAttemptStreamSummaryV1 | null;
};
-export type WorkAttemptEvidenceStateV1 =
+export type WorkAttemptEvidenceStateV1 =
| {
state: "pending";
}
@@ -5557,7 +5791,7 @@ export type WorkAttemptIdentityV1 = {
task_id: TaskId;
};
-export type WorkAttemptListCoverageV1 =
+export type WorkAttemptListCoverageV1 =
| {
coverage: "capped";
remaining: number;
@@ -5576,7 +5810,7 @@ export type WorkAttemptListRequestV1 = {
page_size: number;
};
-export type WorkAttemptListV1 =
+export type WorkAttemptListV1 =
| {
state: "absent";
}
@@ -5600,7 +5834,7 @@ export type WorkAttemptProjectionBindingV1 = {
source_watermark: Record;
};
-export type WorkAttemptProviderOutcomeV1 =
+export type WorkAttemptProviderOutcomeV1 =
| {
outcome: "cancelled";
}
@@ -5735,7 +5969,7 @@ export type WorkCancellationRequestV1 = {
requested_at: UtcMicros;
};
-export type WorkCancellationStateV1 =
+export type WorkCancellationStateV1 =
| {
state: "acknowledged";
value: WorkCancellationAcknowledgementV1;
@@ -5781,7 +6015,7 @@ export type WorkDecompositionProposalV1 = {
rationale: Array;
};
-export type WorkDuplicateAdjudicationAppendOutcomeV1 =
+export type WorkDuplicateAdjudicationAppendOutcomeV1 =
| {
outcome: "appended";
receipt: WorkDuplicateAdjudicationReceiptV1;
@@ -5831,7 +6065,7 @@ export type WorkEffectStateV1 = "compound_non_repeatable" | "intercepted" | "obs
export type WorkEgressPolicy = "allowlisted" | "deny";
-export type WorkEvidenceContinuationV1 =
+export type WorkEvidenceContinuationV1 =
| {
cursor: string;
kind: "anchor";
@@ -5851,7 +6085,7 @@ export type WorkEvidenceCoverageV1 = {
state: WorkEvidenceCoverageStateV1;
};
-export type WorkEvidenceExpansionSelectorV1 =
+export type WorkEvidenceExpansionSelectorV1 =
| {
kind: "anchor";
link_id: string;
@@ -5903,7 +6137,7 @@ export type WorkEvidenceRetrieveRequestV1 = {
verified_version: VerifiedWorkGraphVersionV1;
};
-export type WorkEvidenceSourceV1 =
+export type WorkEvidenceSourceV1 =
| {
hydration: WorkAnchorHydrationV1;
kind: "anchor";
@@ -5940,7 +6174,7 @@ export type WorkExecutionEnvelopeV1 = {
worktree_root: string;
};
-export type WorkExecutionHistoryV1 =
+export type WorkExecutionHistoryV1 =
| {
state: "absent";
}
@@ -5999,7 +6233,7 @@ export type WorkExecutionSpanV1 = {
wall_micros: number | null;
};
-export type WorkExecutionTimingCoverageV1 =
+export type WorkExecutionTimingCoverageV1 =
| {
coverage: "complete";
}
@@ -6018,7 +6252,7 @@ export type WorkExperienceCandidateV1 = {
item: WorkItemV1;
};
-export type WorkExperienceCoverageV1 =
+export type WorkExperienceCoverageV1 =
| {
applicable: number;
coverage: "complete";
@@ -6056,7 +6290,7 @@ export type WorkExperienceV1 = {
verified_version: VerifiedWorkGraphVersionV1;
};
-export type WorkExpertiseAuthorizationV1 =
+export type WorkExpertiseAuthorizationV1 =
| {
availability: "available";
categories: Array;
@@ -6086,7 +6320,7 @@ export type WorkExpertiseLegalActionV1 = "allow_requested_categories" | "grant_p
export type WorkExpertiseUnavailableReasonV1 = "project_consent_disabled" | "project_consent_expired" | "project_consent_not_yet_effective" | "requested_category_not_allowed" | "user_consent_disabled" | "user_consent_expired" | "user_consent_not_yet_effective";
-export type WorkFallbackTopology =
+export type WorkFallbackTopology =
| {
executable: WorkExecutableReference;
kind: "codex_cli";
@@ -6169,7 +6403,7 @@ export type WorkflowFanOutChildPlanV1 = {
task_id: TaskId;
};
-export type WorkflowFanOutFailurePolicyV1 =
+export type WorkflowFanOutFailurePolicyV1 =
| {
policy: "collect";
}
@@ -6232,7 +6466,7 @@ export type WorkflowRunEvent = {
sequence: number;
};
-export type WorkflowRunEventKind =
+export type WorkflowRunEventKind =
| {
definition: WorkflowDefinition;
fan_out_plans: Array;
@@ -6353,7 +6587,7 @@ export type WorkflowSynthesisDraft = {
export type WorkFrontierComparisonV1 = "agree" | "disagree" | "incomparable";
-export type WorkGraphChangeV1 =
+export type WorkGraphChangeV1 =
| {
based_on_version: number;
identity: WorkAttemptIdentityV1;
@@ -6416,7 +6650,7 @@ export type WorkGraphChangeV1 =
proposal_id: ProposalId;
};
-export type WorkGraphReadModeV1 =
+export type WorkGraphReadModeV1 =
| {
mode: "as_of";
valid_at: UtcMicros;
@@ -6442,7 +6676,7 @@ export type WorkGraphReadRequestV1 = {
selection: WorkProductSelectionScopeV1;
};
-export type WorkGraphReadV1 =
+export type WorkGraphReadV1 =
| {
authorized_scope: AuthorizedWorkProductScopeV1;
mode: "absent";
@@ -6473,7 +6707,7 @@ export type WorkGraphReadV1 =
timeline: WorkGraphTimelineV1;
};
-export type WorkGraphSelectionCoverageV1 =
+export type WorkGraphSelectionCoverageV1 =
| {
coverage: "complete";
covered_events: number;
@@ -6485,7 +6719,7 @@ export type WorkGraphSelectionCoverageV1 =
first_excluded_sequence: number;
};
-export type WorkGraphTimelineCoverageV1 =
+export type WorkGraphTimelineCoverageV1 =
| {
coverage: "complete";
returned: number;
@@ -6573,7 +6807,7 @@ export type WorkKanbanProjectionV1 = {
graph_version: number;
};
-export type WorkLeakAdjudicationOutcomeV1 =
+export type WorkLeakAdjudicationOutcomeV1 =
| {
outcome: "appended";
receipt: WorkLeakAdjudicationReceiptV1;
@@ -6653,7 +6887,7 @@ export type WorkPlacementPreflightV1 = {
target: WorkPlacementTargetV1;
};
-export type WorkPlacementReadingV1 =
+export type WorkPlacementReadingV1 =
| {
state: "absent";
}
@@ -6704,7 +6938,7 @@ export type WorkPriorOutcomeV1 = {
export type WorkPriorTerminalV1 = "cancelled" | "failed" | "succeeded" | "timed_out";
-export type WorkProductAuthorizedRelationScopeV1 =
+export type WorkProductAuthorizedRelationScopeV1 =
| {
kind: "project";
project_id: ProjectId;
@@ -6715,7 +6949,7 @@ export type WorkProductAuthorizedRelationScopeV1 =
repository_id: RepositoryId;
};
-export type WorkProductChangeDraftV1 =
+export type WorkProductChangeDraftV1 =
| {
change: "accept_task";
evidence_by_criterion: Record;
@@ -6766,7 +7000,7 @@ export type WorkProductEventEvidenceV1 = {
source_store_id: SourceStoreId;
};
-export type WorkProductEventPayloadV1 =
+export type WorkProductEventPayloadV1 =
| {
change: WorkGraphChangeV1;
kind: "changed";
@@ -6796,7 +7030,7 @@ export type WorkProductEventV1 = {
source_watermark: Record;
};
-export type WorkProductExpectedAuthorityV1 =
+export type WorkProductExpectedAuthorityV1 =
| {
authority: "no_prior_graph";
}
@@ -6831,7 +7065,7 @@ export type WorkProductMutationReceiptV1 = {
verified_graph_version: VerifiedWorkGraphVersionV1;
};
-export type WorkProductMutationRequestV1 =
+export type WorkProductMutationRequestV1 =
| {
mutation: "accept_task";
request: AcceptWorkTaskRequestV1;
@@ -6889,7 +7123,7 @@ export type WorkProductProjectionBundleV1 = {
workload: WorkWorkloadProjectionV1;
};
-export type WorkProductRelationV1 =
+export type WorkProductRelationV1 =
| {
identity: WorkAttemptIdentityV1;
kind: "accepted_attempt";
@@ -6947,7 +7181,7 @@ export type WorkProductRevisionPinsV1 = {
policy_revision_id: string;
};
-export type WorkProductSelectionScopeV1 =
+export type WorkProductSelectionScopeV1 =
| {
selection: "profile_owned_no_git";
}
@@ -7070,7 +7304,7 @@ export type WorkRankedRouteV1 = {
sensitive_data_fitness: WorkOrdinalBandV1;
};
-export type WorkRecoveryStateV1 =
+export type WorkRecoveryStateV1 =
| {
state: "fresh";
}
@@ -7109,7 +7343,7 @@ export type WorkRelationReplanProposalV1 = {
export type WorkRestartReasonV1 = "failure_observed" | "lease_lost" | "process_lost" | "provider_unavailable";
-export type WorkRetryAttemptOutcomeV1 =
+export type WorkRetryAttemptOutcomeV1 =
| {
attempt: WorkAttemptV1;
outcome: "created";
@@ -7141,7 +7375,7 @@ export type WorkRetryReceiptV1 = {
export type WorkRetrySourceV1 = "runtime";
-export type WorkRouteDecisionV1 =
+export type WorkRouteDecisionV1 =
| {
decision: "abstained";
reason: string;
@@ -7170,7 +7404,7 @@ export type WorkRoutePlanV1 = {
export type WorkRunControlAuthorityV1 = number;
-export type WorkRunControlReadingV1 =
+export type WorkRunControlReadingV1 =
| {
control: WorkRunControlV1;
live_attempts: Array;
@@ -7215,7 +7449,7 @@ export type WorkRuntimeAttemptProjectionV1 = {
state: WorkAttemptStateV1;
};
-export type WorkRuntimeProjectionCoverageV1 =
+export type WorkRuntimeProjectionCoverageV1 =
| {
coverage: "complete";
}
@@ -7262,7 +7496,7 @@ export type WorkSubtaskSketchV1 = {
summary: string;
};
-export type WorkSynthesisAttemptV1 =
+export type WorkSynthesisAttemptV1 =
| {
attempt: WorkAttemptV1;
draft: WorkflowSynthesisDraft;
@@ -7289,7 +7523,7 @@ export type WorkSynthesisSourceEnvelopeV1 = {
source: WorkAttemptIdentityV1;
};
-export type WorkSynthesisSourceOutcomeV1 =
+export type WorkSynthesisSourceOutcomeV1 =
| {
evidence: ManifestDigest;
outcome: "cancelled";
@@ -7384,7 +7618,7 @@ export type WorkTaskShapeV1 = {
kind: WorkTaskShapeKindV1;
};
-export type WorkTerminalEvidenceV1 =
+export type WorkTerminalEvidenceV1 =
| {
evidence_digest: ManifestDigest;
observed_at: UtcMicros;
@@ -7467,7 +7701,7 @@ export type WorktreeCleanlinessRequirementV1 = "allow_untracked_only_for_preflig
export type WorktreeId = string;
-export type WorktreePlacementModeV1 =
+export type WorktreePlacementModeV1 =
| {
kind: "configured_root";
root_id: string;
diff --git a/dashboard/src/test/fixtures/settings-patch.json b/dashboard/src/test/fixtures/settings-patch.json
new file mode 100644
index 0000000000..6c5d8473a9
--- /dev/null
+++ b/dashboard/src/test/fixtures/settings-patch.json
@@ -0,0 +1,639 @@
+{
+ "cancelled": {
+ "body": {
+ "contract": {
+ "schema_id": "schema.configuration.batch.result",
+ "schema_revision": 1
+ },
+ "problem": {
+ "cancellation_stage": "before_admission",
+ "code": "cancelled",
+ "committed_receipt": null,
+ "coverage": null,
+ "detail": null,
+ "details": [],
+ "diagnostic": null,
+ "execution_failure_classification": null,
+ "kind": "cancelled",
+ "legal_actions": [],
+ "message": "The request was cancelled before admission",
+ "owning_layer": "application",
+ "request_id": "request.configuration.settings-fixture",
+ "retry": "never",
+ "retry_after_millis": null,
+ "retry_scope": null,
+ "retryable": false,
+ "revision": 1,
+ "terminality": "pre_admission",
+ "trace_id": "request.configuration.settings-fixture",
+ "unavailable_classification": null
+ },
+ "request_id": "request.configuration.settings-fixture"
+ },
+ "status": 409
+ },
+ "changed": {
+ "body": {
+ "application_outcome": {
+ "outcome": "effect",
+ "value": {
+ "authority": {
+ "authorized_scope_digest": "sha256:cb520e3731e8dbab143f4fc32b885fc977d467f4648a6429bf0ed9bf0294873e",
+ "disclosure": "metadata",
+ "grant_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "grant_id": "grant.settings",
+ "grant_revision": 1,
+ "policy": {
+ "decision_id": "policy.settings",
+ "digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "evaluator_revision": "policy.settings.v1",
+ "revision": 1
+ },
+ "revalidated_at": 10
+ },
+ "effect_class": "administrative",
+ "effect_id": "effect.configuration.settings",
+ "execution": {
+ "budget": {
+ "bytes_consumed": 0,
+ "elapsed_micros": 0,
+ "units_consumed": 0
+ },
+ "cancellation": null,
+ "effective_deadline": {
+ "expires_at": 1000
+ },
+ "ended_at": 20,
+ "started_at": 10,
+ "termination": "completed"
+ },
+ "expected_state": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
+ "idempotency_key": "settings-idempotency-fixture",
+ "payload": null,
+ "receipt": {
+ "actor": "actor.settings",
+ "catalog_digest": "sha256:eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
+ "committed_state": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
+ "configuration_digest": "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
+ "effect_class": "administrative",
+ "expected_state": "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
+ "external_proof": null,
+ "idempotency_key": "settings-idempotency-fixture",
+ "input_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
+ "operation": "use-case.configuration.batch",
+ "outcome": "completed",
+ "policy_digest": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
+ "privacy_digest": "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
+ "request_id": "request.configuration.settings-fixture",
+ "scope": {
+ "project_id": "project.settings.scout",
+ "reference": null,
+ "repository_id": "repository.settings",
+ "scope_digest": "sha256:cb520e3731e8dbab143f4fc32b885fc977d467f4648a6429bf0ed9bf0294873e",
+ "worktree_id": "worktree.settings"
+ }
+ },
+ "reconciliation": "reconciled"
+ }
+ },
+ "current": {
+ "authorization": {
+ "outcome": "authorized"
+ },
+ "coverage": {
+ "completeness": "complete",
+ "denominator": 1,
+ "eligible": 1,
+ "examined": 1,
+ "excluded": 0,
+ "matched": 1,
+ "omission_reasons": [],
+ "omitted": 0,
+ "unit": "configuration_control_plane",
+ "unknown": 0
+ },
+ "domain_state": "ready",
+ "freshness": {
+ "observed_at_micros": 100,
+ "state": "fresh",
+ "watermark": null
+ },
+ "legal_actions": [
+ {
+ "kind": "request_apply",
+ "operation": "configuration_batch"
+ },
+ {
+ "kind": "refresh",
+ "operation": "configuration_list"
+ }
+ ],
+ "payload": {
+ "automation": {
+ "availability": {
+ "available": true
+ },
+ "backend": "codex_app_server",
+ "config_endpoint": "/api/plugins/holographic/curation/config",
+ "configuration_revision_id": "rev-43",
+ "enabled": true,
+ "host_mode": "standalone"
+ },
+ "environment": {
+ "global_accounting_enabled": false,
+ "global_accounting_mode": "disabled",
+ "pricing_offline": true,
+ "variables": []
+ },
+ "project": {
+ "config": {
+ "context_scout": false,
+ "exclude": [
+ ".git/**",
+ ".tracedecay/**",
+ "**/*.min.*",
+ ".cache/**",
+ "**/.cache/**",
+ ".gradle/**",
+ "**/.gradle/**",
+ ".next/**",
+ "**/.next/**",
+ ".turbo/**",
+ "**/.turbo/**",
+ ".venv/**",
+ "**/.venv/**",
+ ".worktrees/**",
+ "**/.worktrees/**",
+ "__pycache__/**",
+ "**/__pycache__/**",
+ "build/**",
+ "**/build/**",
+ "coverage/**",
+ "**/coverage/**",
+ "dist/**",
+ "**/dist/**",
+ "node_modules/**",
+ "**/node_modules/**",
+ "out/**",
+ "**/out/**",
+ "target/**",
+ "**/target/**",
+ "vendor/**",
+ "**/vendor/**",
+ "venv/**",
+ "**/venv/**"
+ ],
+ "extract_docstrings": true,
+ "include": [],
+ "max_file_size": 2097152,
+ "sync": {
+ "auto_track_pr_branches": false,
+ "auto_track_pr_poll_secs": 300
+ },
+ "telemetry": {
+ "timings": true
+ },
+ "track_call_sites": true
+ },
+ "configuration_revision_id": "rev-43",
+ "configuration_snapshot_id": "tracedecay.configuration.snapshot.v1.fd56d9635ef3558e8743096f4bcc8dc5f6e38a457b3b225086af5bf4b01b75ab",
+ "pr_autotrack": {
+ "tracked": []
+ }
+ },
+ "resync_recommended": true,
+ "storage": {
+ "dashboard_root": "/project",
+ "graph_db": "/project/.tracedecay/graph.db",
+ "lcm_db": "/project/.tracedecay/lcm.db",
+ "lcm_scope": "project",
+ "memory_db": "/project/.tracedecay/memory.db",
+ "project_id": "project.settings.scout",
+ "project_root": "/project",
+ "savings_db": "/project/.tracedecay/savings.db",
+ "storage_mode": "project_local",
+ "store_root": "/project/.tracedecay"
+ },
+ "user": {
+ "code_index_worker_configuration_revision_id": "workers-rev-3",
+ "code_index_worker_configuration_snapshot_id": "workers-snapshot-3",
+ "code_index_worker_status": null,
+ "code_index_workers": {
+ "mode": "automatic"
+ },
+ "configuration_revision_id": "user-rev-7",
+ "configuration_snapshot_id": "user-snapshot-7",
+ "extraction_timeout_secs": 30,
+ "installed_agents": [],
+ "upload_enabled": false,
+ "watcher_debounce": "15s"
+ },
+ "version": {
+ "cached_latest_version": null,
+ "channel": "stable",
+ "version": "0.1.0"
+ }
+ },
+ "schema_revision": 1,
+ "scope": {
+ "project_id": "project.settings.scout",
+ "storage_mode": "project_local",
+ "store_root": "/project/.tracedecay"
+ },
+ "source_watermark": null,
+ "time": {
+ "observation_time_micros": 100,
+ "valid_time_micros": null
+ },
+ "version": {
+ "entity_version": null,
+ "graph_version": null
+ }
+ }
+ },
+ "status": 200
+ },
+ "idempotency_conflict": {
+ "body": {
+ "contract": {
+ "schema_id": "schema.configuration.batch.result",
+ "schema_revision": 1
+ },
+ "problem": {
+ "cancellation_stage": null,
+ "code": "configuration.conflict",
+ "committed_receipt": null,
+ "coverage": null,
+ "detail": null,
+ "details": [],
+ "diagnostic": {
+ "code": "configuration.conflict",
+ "message": "The idempotency key was already used for a different settings change."
+ },
+ "execution_failure_classification": null,
+ "kind": "conflict",
+ "legal_actions": [
+ "refresh"
+ ],
+ "message": "The idempotency key was already used for a different settings change.",
+ "owning_layer": "application",
+ "request_id": "request.configuration.settings-fixture",
+ "retry": "after_revalidate",
+ "retry_after_millis": null,
+ "retry_scope": "fresh_request",
+ "retryable": true,
+ "revision": 1,
+ "terminality": "pre_admission",
+ "trace_id": "request.configuration.settings-fixture",
+ "unavailable_classification": null
+ },
+ "request_id": "request.configuration.settings-fixture"
+ },
+ "status": 409
+ },
+ "read": {
+ "body": {
+ "authorization": {
+ "outcome": "authorized"
+ },
+ "coverage": {
+ "completeness": "complete",
+ "denominator": 1,
+ "eligible": 1,
+ "examined": 1,
+ "excluded": 0,
+ "matched": 1,
+ "omission_reasons": [],
+ "omitted": 0,
+ "unit": "configuration_control_plane",
+ "unknown": 0
+ },
+ "domain_state": "ready",
+ "freshness": {
+ "observed_at_micros": 100,
+ "state": "fresh",
+ "watermark": null
+ },
+ "legal_actions": [
+ {
+ "kind": "request_apply",
+ "operation": "configuration_batch"
+ },
+ {
+ "kind": "refresh",
+ "operation": "configuration_list"
+ }
+ ],
+ "payload": {
+ "automation": {
+ "availability": {
+ "available": true
+ },
+ "backend": "codex_app_server",
+ "config_endpoint": "/api/plugins/holographic/curation/config",
+ "configuration_revision_id": "rev-42",
+ "enabled": true,
+ "host_mode": "standalone"
+ },
+ "environment": {
+ "global_accounting_enabled": false,
+ "global_accounting_mode": "disabled",
+ "pricing_offline": true,
+ "variables": []
+ },
+ "project": {
+ "config": {
+ "context_scout": false,
+ "exclude": [
+ ".git/**",
+ ".tracedecay/**",
+ "**/*.min.*",
+ ".cache/**",
+ "**/.cache/**",
+ ".gradle/**",
+ "**/.gradle/**",
+ ".next/**",
+ "**/.next/**",
+ ".turbo/**",
+ "**/.turbo/**",
+ ".venv/**",
+ "**/.venv/**",
+ ".worktrees/**",
+ "**/.worktrees/**",
+ "__pycache__/**",
+ "**/__pycache__/**",
+ "build/**",
+ "**/build/**",
+ "coverage/**",
+ "**/coverage/**",
+ "dist/**",
+ "**/dist/**",
+ "node_modules/**",
+ "**/node_modules/**",
+ "out/**",
+ "**/out/**",
+ "target/**",
+ "**/target/**",
+ "vendor/**",
+ "**/vendor/**",
+ "venv/**",
+ "**/venv/**"
+ ],
+ "extract_docstrings": true,
+ "include": [],
+ "max_file_size": 1048576,
+ "sync": {
+ "auto_track_pr_branches": false,
+ "auto_track_pr_poll_secs": 300
+ },
+ "telemetry": {
+ "timings": true
+ },
+ "track_call_sites": true
+ },
+ "configuration_revision_id": "rev-42",
+ "configuration_snapshot_id": "tracedecay.configuration.snapshot.v1.fd56d9635ef3558e8743096f4bcc8dc5f6e38a457b3b225086af5bf4b01b75ab",
+ "pr_autotrack": {
+ "tracked": []
+ }
+ },
+ "resync_recommended": false,
+ "storage": {
+ "dashboard_root": "/project",
+ "graph_db": "/project/.tracedecay/graph.db",
+ "lcm_db": "/project/.tracedecay/lcm.db",
+ "lcm_scope": "project",
+ "memory_db": "/project/.tracedecay/memory.db",
+ "project_id": "project.settings.scout",
+ "project_root": "/project",
+ "savings_db": "/project/.tracedecay/savings.db",
+ "storage_mode": "project_local",
+ "store_root": "/project/.tracedecay"
+ },
+ "user": {
+ "code_index_worker_configuration_revision_id": "workers-rev-3",
+ "code_index_worker_configuration_snapshot_id": "workers-snapshot-3",
+ "code_index_worker_status": null,
+ "code_index_workers": {
+ "mode": "automatic"
+ },
+ "configuration_revision_id": "user-rev-7",
+ "configuration_snapshot_id": "user-snapshot-7",
+ "extraction_timeout_secs": 30,
+ "installed_agents": [],
+ "upload_enabled": false,
+ "watcher_debounce": "15s"
+ },
+ "version": {
+ "cached_latest_version": null,
+ "channel": "stable",
+ "version": "0.1.0"
+ }
+ },
+ "schema_revision": 1,
+ "scope": {
+ "project_id": "project.settings.scout",
+ "storage_mode": "project_local",
+ "store_root": "/project/.tracedecay"
+ },
+ "source_watermark": null,
+ "time": {
+ "observation_time_micros": 100,
+ "valid_time_micros": null
+ },
+ "version": {
+ "entity_version": null,
+ "graph_version": null
+ }
+ },
+ "status": 200
+ },
+ "revision_conflict": {
+ "body": {
+ "actual_revision_id": "rev-43",
+ "code": "configuration_revision_conflict",
+ "detail": "settings changed after this edit began; refresh and retry",
+ "expected_revision_id": "rev-42"
+ },
+ "status": 409
+ },
+ "unavailable": {
+ "body": {
+ "code": "configuration_authority_unavailable",
+ "detail": "configuration authority is unavailable"
+ },
+ "status": 503
+ },
+ "unchanged": {
+ "body": {
+ "application_outcome": null,
+ "current": {
+ "authorization": {
+ "outcome": "authorized"
+ },
+ "coverage": {
+ "completeness": "complete",
+ "denominator": 1,
+ "eligible": 1,
+ "examined": 1,
+ "excluded": 0,
+ "matched": 1,
+ "omission_reasons": [],
+ "omitted": 0,
+ "unit": "configuration_control_plane",
+ "unknown": 0
+ },
+ "domain_state": "ready",
+ "freshness": {
+ "observed_at_micros": 100,
+ "state": "fresh",
+ "watermark": null
+ },
+ "legal_actions": [
+ {
+ "kind": "request_apply",
+ "operation": "configuration_batch"
+ },
+ {
+ "kind": "refresh",
+ "operation": "configuration_list"
+ }
+ ],
+ "payload": {
+ "automation": {
+ "availability": {
+ "available": true
+ },
+ "backend": "codex_app_server",
+ "config_endpoint": "/api/plugins/holographic/curation/config",
+ "configuration_revision_id": "rev-42",
+ "enabled": true,
+ "host_mode": "standalone"
+ },
+ "environment": {
+ "global_accounting_enabled": false,
+ "global_accounting_mode": "disabled",
+ "pricing_offline": true,
+ "variables": []
+ },
+ "project": {
+ "config": {
+ "context_scout": false,
+ "exclude": [
+ ".git/**",
+ ".tracedecay/**",
+ "**/*.min.*",
+ ".cache/**",
+ "**/.cache/**",
+ ".gradle/**",
+ "**/.gradle/**",
+ ".next/**",
+ "**/.next/**",
+ ".turbo/**",
+ "**/.turbo/**",
+ ".venv/**",
+ "**/.venv/**",
+ ".worktrees/**",
+ "**/.worktrees/**",
+ "__pycache__/**",
+ "**/__pycache__/**",
+ "build/**",
+ "**/build/**",
+ "coverage/**",
+ "**/coverage/**",
+ "dist/**",
+ "**/dist/**",
+ "node_modules/**",
+ "**/node_modules/**",
+ "out/**",
+ "**/out/**",
+ "target/**",
+ "**/target/**",
+ "vendor/**",
+ "**/vendor/**",
+ "venv/**",
+ "**/venv/**"
+ ],
+ "extract_docstrings": true,
+ "include": [],
+ "max_file_size": 1048576,
+ "sync": {
+ "auto_track_pr_branches": false,
+ "auto_track_pr_poll_secs": 300
+ },
+ "telemetry": {
+ "timings": true
+ },
+ "track_call_sites": true
+ },
+ "configuration_revision_id": "rev-42",
+ "configuration_snapshot_id": "tracedecay.configuration.snapshot.v1.fd56d9635ef3558e8743096f4bcc8dc5f6e38a457b3b225086af5bf4b01b75ab",
+ "pr_autotrack": {
+ "tracked": []
+ }
+ },
+ "resync_recommended": false,
+ "storage": {
+ "dashboard_root": "/project",
+ "graph_db": "/project/.tracedecay/graph.db",
+ "lcm_db": "/project/.tracedecay/lcm.db",
+ "lcm_scope": "project",
+ "memory_db": "/project/.tracedecay/memory.db",
+ "project_id": "project.settings.scout",
+ "project_root": "/project",
+ "savings_db": "/project/.tracedecay/savings.db",
+ "storage_mode": "project_local",
+ "store_root": "/project/.tracedecay"
+ },
+ "user": {
+ "code_index_worker_configuration_revision_id": "workers-rev-3",
+ "code_index_worker_configuration_snapshot_id": "workers-snapshot-3",
+ "code_index_worker_status": null,
+ "code_index_workers": {
+ "mode": "automatic"
+ },
+ "configuration_revision_id": "user-rev-7",
+ "configuration_snapshot_id": "user-snapshot-7",
+ "extraction_timeout_secs": 30,
+ "installed_agents": [],
+ "upload_enabled": false,
+ "watcher_debounce": "15s"
+ },
+ "version": {
+ "cached_latest_version": null,
+ "channel": "stable",
+ "version": "0.1.0"
+ }
+ },
+ "schema_revision": 1,
+ "scope": {
+ "project_id": "project.settings.scout",
+ "storage_mode": "project_local",
+ "store_root": "/project/.tracedecay"
+ },
+ "source_watermark": null,
+ "time": {
+ "observation_time_micros": 100,
+ "valid_time_micros": null
+ },
+ "version": {
+ "entity_version": null,
+ "graph_version": null
+ }
+ }
+ },
+ "status": 200
+ },
+ "validation": {
+ "body": {
+ "detail": "invalid project settings patch: unknown field `unknown_setting`, expected one of `expected_revision_id`, `idempotency_key`, `include`, `exclude`, `max_file_size`, `extract_docstrings`, `track_call_sites`, `telemetry`, `sync`, `context_scout`, `context_scout_intent`",
+ "validation_errors": [
+ {
+ "field": "unknown_setting",
+ "message": "invalid project settings patch: unknown field `unknown_setting`, expected one of `expected_revision_id`, `idempotency_key`, `include`, `exclude`, `max_file_size`, `extract_docstrings`, `track_call_sites`, `telemetry`, `sync`, `context_scout`, `context_scout_intent`"
+ }
+ ]
+ },
+ "status": 400
+ }
+}
diff --git a/dashboard/src/workspaces/settings/SettingsPage.dom.test.tsx b/dashboard/src/workspaces/settings/SettingsPage.dom.test.tsx
index 370012bae5..27715acba8 100644
--- a/dashboard/src/workspaces/settings/SettingsPage.dom.test.tsx
+++ b/dashboard/src/workspaces/settings/SettingsPage.dom.test.tsx
@@ -2,6 +2,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { act, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
+import settingsWire from '../../test/fixtures/settings-patch.json';
import { FIXTURES } from '../../../stories/fixtures/data.ts';
import { useScope, type ScopeWritability } from '../../data/scope/store.ts';
import { SettingsPage, findConfigSection } from './SettingsPage.tsx';
@@ -97,15 +98,7 @@ const FIXTURE_SETTINGS: ReadonlyArray = [
function projectPatchResponse(current: unknown) {
return {
- application_outcome: {
- Effect: {
- receipt: {
- request_id: 'request.configuration.settings-fixture',
- idempotency_key: 'settings-fixture',
- outcome: 'completed',
- },
- },
- },
+ application_outcome: structuredClone(settingsWire.changed.body.application_outcome),
current,
};
}
@@ -402,6 +395,35 @@ describe('SettingsPage effective configuration review', () => {
).toBeTruthy();
});
+ it('requires a refresh after an uncertain effect without offering to repeat the write', async () => {
+ const response = structuredClone(settingsWire.changed.body);
+ response.application_outcome.value.reconciliation = 'pending';
+ let dispatched = false;
+ const fetch = vi.fn(async (_input: RequestInfo | URL, init?: RequestInit) => {
+ if (init?.method === 'PATCH') {
+ dispatched = true;
+ return jsonResponse(response);
+ }
+ return jsonResponse(dispatched ? updatedSettings('rev-43') : settings());
+ });
+ vi.stubGlobal('fetch', fetch);
+ const user = userEvent.setup();
+ renderSettings();
+ await openReview(user, MAX_FILE_SIZE);
+ await user.clear(proposal(MAX_FILE_SIZE));
+ await user.type(proposal(MAX_FILE_SIZE), '2097152');
+ await user.click(screen.getByRole('button', { name: 'Review project change' }));
+ await user.click(screen.getByRole('checkbox', { name: /I confirm this change against configuration revision rev-42/ }));
+ await user.click(screen.getByRole('button', { name: 'Apply project settings' }));
+ expect(await screen.findByText(/Refresh current values before retrying/)).toBeTruthy();
+ expect(screen.queryByText('Project settings saved')).toBeNull();
+ expect(screen.queryByRole('button', { name: 'Retry project settings' })).toBeNull();
+ await user.click(screen.getByRole('button', { name: 'Load current values' }));
+ await waitFor(() => expect(within(row(MAX_FILE_SIZE)).getByText('2,097,152')).toBeTruthy());
+ expect(fetch.mock.calls.filter(([, init]) => init?.method === 'PATCH')).toHaveLength(1);
+ expect(screen.queryByText('Project settings saved')).toBeNull();
+ });
+
it('blocks a stale project change before sending the patch', async () => {
let getCount = 0;
const methods: string[] = [];
diff --git a/dashboard/src/workspaces/settings/SettingsReviewPanel.tsx b/dashboard/src/workspaces/settings/SettingsReviewPanel.tsx
index 12a5b195c2..e2ae53be1f 100644
--- a/dashboard/src/workspaces/settings/SettingsReviewPanel.tsx
+++ b/dashboard/src/workspaces/settings/SettingsReviewPanel.tsx
@@ -397,7 +397,10 @@ function FrozenReview({
}) {
const confirmed = settingsConfirmationHeld(state);
const applying = state.status === 'submitting';
- const resolvable = state.status === 'conflicted' || state.status === 'review_superseded';
+ const resolvable = state.status === 'conflicted'
+ || state.status === 'review_superseded'
+ || (state.status === 'submit_failed'
+ && (state.failure.kind === 'protocol_error' || state.failure.kind === 'unsettled'));
const retryable = state.status === 'authority_withdrawn' || state.status === 'submit_failed';
return (
diff --git a/dashboard/src/workspaces/settings/settingsEditorMachine.ts b/dashboard/src/workspaces/settings/settingsEditorMachine.ts
index c893afde88..fbccddee59 100644
--- a/dashboard/src/workspaces/settings/settingsEditorMachine.ts
+++ b/dashboard/src/workspaces/settings/settingsEditorMachine.ts
@@ -108,7 +108,7 @@ type SettingsResting =
/** A write that did not reach a verdict. Distinct from a refusal, which did. */
type SettingsSubmitFailure =
| { readonly kind: 'offline'; readonly detail: string }
- | { readonly kind: 'error'; readonly detail: string }
+ | { readonly kind: 'error' | 'unsettled'; readonly detail: string }
| { readonly kind: 'protocol_error'; readonly authority: string; readonly detail: string };
interface SettingsEditable {
@@ -453,6 +453,8 @@ function settleSubmit(
return failed(state, { kind: 'offline', detail: result.detail });
case 'error':
return failed(state, { kind: 'error', detail: result.detail });
+ case 'unsettled':
+ return failed(state, { kind: 'unsettled', detail: result.detail });
case 'protocol_error':
return failed(state, {
kind: 'protocol_error',
diff --git a/dashboard/src/workspaces/settings/settingsMutation.test.ts b/dashboard/src/workspaces/settings/settingsMutation.test.ts
new file mode 100644
index 0000000000..dae27d29a4
--- /dev/null
+++ b/dashboard/src/workspaces/settings/settingsMutation.test.ts
@@ -0,0 +1,89 @@
+import { afterEach, describe, expect, it, vi } from 'vitest';
+import wire from '../../test/fixtures/settings-patch.json';
+import { applySettingsMutation } from './settingsMutation.ts';
+
+// settings_api/tests/wire.rs serializes these responses through Axum. Keep
+// the complete receipts so decoding exercises the producer contract.
+const request = {
+ writability: { state: 'writable', target: 'tracedecay' },
+ scope: 'project',
+ expectedRevisionId: 'rev-42',
+ idempotencyKey: 'settings-idempotency-fixture',
+ readUrl: '/api/settings',
+ patchUrl: '/api/settings/project',
+ patch: { max_file_size: 2_097_152 },
+} as const;
+
+async function apply(response: { status: number; body: unknown }) {
+ const fetch = vi.fn()
+ .mockResolvedValueOnce(Response.json(wire.read.body, { status: wire.read.status }))
+ .mockResolvedValueOnce(Response.json(response.body, { status: response.status }));
+ vi.stubGlobal('fetch', fetch);
+ const result = await applySettingsMutation(request);
+ expect(fetch).toHaveBeenCalledTimes(2);
+ return result;
+}
+
+afterEach(() => vi.unstubAllGlobals());
+
+describe('settings producer/consumer settlement', () => {
+ it('accepts the committed effect and preserves its complete durable receipt', async () => {
+ expect(await apply(wire.changed)).toMatchObject({
+ outcome: 'success',
+ revisionId: 'rev-43',
+ effectReceipt: wire.changed.body.application_outcome.value.receipt,
+ });
+ });
+
+ it('accepts the explicit null outcome for an already-current patch', async () => {
+ expect(await apply(wire.unchanged)).toMatchObject({ outcome: 'success', revisionId: 'rev-42', effectReceipt: null });
+ });
+
+ it('requires the explicit nullable settlement and the canonical effect shape', async () => {
+ for (const body of [
+ { current: wire.changed.body.current },
+ { ...wire.changed.body, application_outcome: { Effect: wire.changed.body.application_outcome.value } },
+ ]) {
+ expect(await apply({ status: 200, body })).toMatchObject({ outcome: 'protocol_error' });
+ }
+ });
+
+ it('does not present uncertain or unproven effects as applied', async () => {
+ for (const mutate of [
+ (effect: typeof wire.changed.body.application_outcome.value) => { effect.execution.termination = 'effect_unknown'; },
+ (effect: typeof wire.changed.body.application_outcome.value) => { effect.reconciliation = 'pending'; },
+ (effect: typeof wire.changed.body.application_outcome.value) => { effect.receipt.outcome = 'partial'; },
+ (effect: typeof wire.changed.body.application_outcome.value) => { Object.assign(effect.receipt, { committed_state: null }); },
+ ]) {
+ const response = structuredClone(wire.changed);
+ mutate(response.body.application_outcome.value);
+ expect(await apply(response)).toMatchObject({ outcome: 'unsettled', detail: expect.stringContaining('Refresh current values before retrying') });
+ }
+ });
+
+ it('retains validation, revision conflicts and unavailable authority semantics', async () => {
+ expect(await apply(wire.validation)).toMatchObject({ outcome: 'validation', errors: wire.validation.body.validation_errors });
+ expect(await apply(wire.revision_conflict)).toEqual({ outcome: 'conflict', expectedRevisionId: 'rev-42', actualRevisionId: 'rev-43' });
+ expect(await apply(wire.unavailable)).toMatchObject({ outcome: 'unavailable' });
+ });
+
+ it('keeps idempotency conflict and cancellation messages distinct from revision conflicts', async () => {
+ for (const response of [wire.idempotency_conflict, wire.cancelled]) {
+ expect(await apply(response)).toEqual({ outcome: 'error', detail: response.body.problem.message });
+ }
+ });
+
+ it('requires current values before retrying a problem carrying a committed effect', async () => {
+ const response = structuredClone(wire.idempotency_conflict);
+ Object.assign(response.body.problem, {
+ kind: 'partial_effect',
+ committed_receipt: { ...wire.changed.body.application_outcome.value.receipt, outcome: 'partial' },
+ });
+ expect(await apply(response)).toMatchObject({ outcome: 'unsettled', detail: expect.stringContaining('Refresh current values before retrying') });
+ });
+
+ it('rejects problem/status disagreement and malformed refusals', async () => {
+ expect(await apply({ ...wire.idempotency_conflict, status: 503 })).toMatchObject({ outcome: 'protocol_error' });
+ expect(await apply({ status: 409, body: {} })).toMatchObject({ outcome: 'protocol_error' });
+ });
+});
diff --git a/dashboard/src/workspaces/settings/settingsMutation.ts b/dashboard/src/workspaces/settings/settingsMutation.ts
index e6b90c6a38..7ad06da6dd 100644
--- a/dashboard/src/workspaces/settings/settingsMutation.ts
+++ b/dashboard/src/workspaces/settings/settingsMutation.ts
@@ -1,6 +1,11 @@
import { dashboardFetch } from '../../data/auth/credential.ts';
-import { z } from 'zod';
-import type { SettingsPayloadV1 } from '../../contracts/generated.ts';
+import {
+ DashboardConfigurationRefusalV1Schema,
+ ProjectSettingsPatchResponseV1Schema,
+ type EffectReceipt,
+ type SettingsPayloadV1,
+} from '../../contracts/generated.ts';
+import { applicationProblemStatusMatches } from '../../contracts/application.ts';
import { readOnlyScopeRefusal, type ScopeWritability } from '../../data/scope/store.ts';
import {
buildSettingsEditor,
@@ -25,7 +30,7 @@ export type SettingsMutationResult =
readonly resyncRecommended: boolean;
readonly restartRecommended: boolean;
/** Canonical durable application receipt for project writes. */
- readonly effectReceipt: Readonly> | null;
+ readonly effectReceipt: EffectReceipt | null;
}
| {
readonly outcome: 'conflict';
@@ -41,6 +46,10 @@ export type SettingsMutationResult =
readonly outcome: 'offline' | 'error';
readonly detail: string;
}
+ | {
+ readonly outcome: 'unsettled';
+ readonly detail: string;
+ }
/** The authority that performs this write is not currently mounted. Kept
* apart from `error`: nothing was attempted, and nothing changed. */
| {
@@ -127,12 +136,6 @@ export async function applySettingsMutation(
}),
});
if (patched.outcome !== 'response') return patched;
- if (patched.response.status === 409) {
- return readConflict(patched.body, request.expectedRevisionId);
- }
- if (patched.response.status === 503) {
- return { outcome: 'unavailable', detail: unavailableDetail(patched.body) };
- }
// The gateway's read-only refusal, recognized by its own body rather than by
// the status alone: a 405 this dashboard cannot account for stays a plain
// error below rather than borrowing the scope explanation.
@@ -144,13 +147,10 @@ export async function applySettingsMutation(
detail: `Nothing was applied: ${scopeRefused.detail.replace(/\.$/, '')}.`,
};
}
+ return { outcome: 'error', detail: 'Settings update failed (HTTP 405).' };
}
if (!patched.response.ok) {
- const validation = readValidation(patched.body);
- return validation ?? {
- outcome: 'error',
- detail: `Settings update failed (HTTP ${patched.response.status}).`,
- };
+ return settingsRefusal(patched.body, patched.response.status, patchAuthority);
}
const projectPatch = request.scope === 'project'
? projectSettingsPatchResponse(patched.body, patchAuthority)
@@ -186,23 +186,10 @@ function projectSettingsPatchResponse(
| {
readonly parsed: true;
readonly current: unknown;
- readonly effectReceipt: Readonly> | null;
+ readonly effectReceipt: EffectReceipt | null;
}
- | { readonly parsed: false; readonly violation: Extract } {
- const response = z
- .object({
- application_outcome: z.union([
- z.null(),
- z.object({
- Effect: z.object({
- receipt: z.record(z.string(), z.unknown()),
- }).passthrough(),
- }).strict(),
- ]),
- current: z.unknown(),
- })
- .strict()
- .safeParse(body);
+ | { readonly parsed: false; readonly violation: Extract } {
+ const response = ProjectSettingsPatchResponseV1Schema.safeParse(body);
if (!response.success) {
return {
parsed: false,
@@ -212,10 +199,23 @@ function projectSettingsPatchResponse(
),
};
}
+ const outcome = response.data.application_outcome;
+ if (outcome !== null && (
+ outcome.outcome !== 'effect'
+ || outcome.value.execution.termination !== 'completed'
+ || outcome.value.reconciliation !== 'reconciled'
+ || outcome.value.receipt.outcome !== 'completed'
+ || outcome.value.receipt.committed_state === null
+ )) {
+ return {
+ parsed: false,
+ violation: { outcome: 'unsettled', detail: 'The settings effect is not confirmed. Refresh current values before retrying.' },
+ };
+ }
return {
parsed: true,
current: response.data.current,
- effectReceipt: response.data.application_outcome?.Effect.receipt ?? null,
+ effectReceipt: outcome?.outcome === 'effect' ? outcome.value.receipt : null,
};
}
@@ -282,42 +282,38 @@ function contractedPayload(body: unknown, authority: string): ContractedPayload
}
}
-/**
- * CONTRACT GAP, the settings refusal bodies are not generated.
- *
- * `/api/settings/{project,user}` build their 400/409/503 bodies with
- * `serde_json::json!` in `crates/tracedecay-api/src/configuration.rs`
- * (`settings_validation_error`, `configuration_revision_conflict_error`,
- * `configuration_authority_unavailable_error`), so schemars never sees them
- * and `contracts/generated.ts` carries no type for them. The success payload
- * is contracted and is parsed as such above; only the refusals are read here.
- *
- * Every member below is therefore optional and every absence resolves to a
- * stated unavailable value, a missing `actual_revision_id` reads as unknown,
- * a missing `detail` says the authority is unavailable without naming which.
- * None of it is inferred, and none of it turns a refusal into a success.
- */
-const RefusalBodySchema = z
- .object({
- detail: z.string().optional(),
- expected_revision_id: z.string().optional(),
- actual_revision_id: z.string().optional(),
- validation_errors: z
- .array(z.object({ field: z.string(), message: z.string() }).passthrough())
- .optional(),
- })
- .passthrough();
-
-function readRefusal(body: unknown): z.infer