Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
cb20d73
fix(evals): bound HTTP decision response memory
ScriptedAlchemy Nov 5, 2026
68b4ef8
chore(deps): sync Bazel dependency updates into integration
ScriptedAlchemy Nov 5, 2026
76630b9
fix(evals): require typed Noul responses for HTTP evaluation
ScriptedAlchemy Nov 5, 2026
e498833
fix(evals): reject ambiguous JSON decision responses
ScriptedAlchemy Nov 5, 2026
2bcc5c5
fix(evals): reject ambiguous JSON inputs and scoring evidence
ScriptedAlchemy Nov 6, 2026
ff8ca25
chore(deps): sync merged Renovate updates into integration
ScriptedAlchemy Nov 6, 2026
203d0dd
fix(evals): bind sampled HTTP responses to durable experiment evidence
ScriptedAlchemy Nov 6, 2026
0582282
chore(deps): integrate pnpm 12.18.0 master update
ScriptedAlchemy Nov 6, 2026
20f03be
chore(deps): integrate msw update into application branch
ScriptedAlchemy Nov 6, 2026
115c410
fix(evals): bound untrusted resume and sample file reads
ScriptedAlchemy Nov 6, 2026
71183f3
fix(evals): report measured p90 and p95 tail latency
ScriptedAlchemy Nov 6, 2026
50c5499
fix(evals): refuse malformed HTTP timing and subset documents
ScriptedAlchemy Nov 6, 2026
43c9c83
fix(evals): refuse late first-response captures on resume
ScriptedAlchemy Nov 6, 2026
ee8b42b
fix(evals): keep raw samples out of scored results
ScriptedAlchemy Nov 6, 2026
5c94df3
fix(evals): require first-case identity for resumed raw samples
ScriptedAlchemy Nov 6, 2026
36233ac
fix(evals): reject overflowing JSON numbers in evidence
ScriptedAlchemy Nov 6, 2026
4930782
docs: remove stale code storage design passages
ScriptedAlchemy Nov 6, 2026
576b025
fix(evals): refuse recapturing a vanished first sample
ScriptedAlchemy Nov 6, 2026
81e9db4
docs(storage): state that store budgets are not enforced yet
ScriptedAlchemy Nov 6, 2026
a0dc81f
Merge pull request #4783 from ScriptedAlchemy/docs/remove-stale-stora…
ScriptedAlchemy Nov 6, 2026
740b952
fix(automation): configure scheduler test backends explicitly
ScriptedAlchemy Nov 6, 2026
bc20b58
fix(evals): repair HTTP runner regression fixtures
ScriptedAlchemy Nov 6, 2026
9cf67ec
fix(evals): require HTTP response fingerprints for durable evidence
ScriptedAlchemy Nov 6, 2026
25c8701
fix(storage): deduplicate SQLite TEMP spill descriptors
ScriptedAlchemy Nov 6, 2026
3edf4b8
fix(storage): exclude non-file SQLite TEMP fd targets
ScriptedAlchemy Nov 6, 2026
6459ca2
fix(sqlite): preserve live writer locks across verified readers
ScriptedAlchemy Nov 6, 2026
abdbe4e
fix(storage): align verified-reader lifetime test with Unix locking
ScriptedAlchemy Nov 6, 2026
dd162ef
fix(storage): bound sealed census to its own directory
ScriptedAlchemy Nov 6, 2026
91e146c
style(sessions): format Unix SQLite reader fixture
ScriptedAlchemy Nov 6, 2026
a121f85
fix(graph): attribute shared sealed bytes to live heads
ScriptedAlchemy Nov 6, 2026
df7ea08
fix(storage): preserve newer and unknown sealed artifacts
ScriptedAlchemy Nov 6, 2026
bab192a
style(graph): format shared-inode census regression
ScriptedAlchemy Nov 6, 2026
4a26c92
fix(graph): refuse symlinked sealed roots during cleanup
ScriptedAlchemy Nov 6, 2026
ab68755
fix(graph): preserve unverified seals during format reset
ScriptedAlchemy Nov 6, 2026
d0a09f6
docs(graph): keep census contract on its entry point
ScriptedAlchemy Nov 6, 2026
fb80599
fix(graph-db): require bound sealed census receipts
ScriptedAlchemy Nov 6, 2026
046bc85
fix(graph): bind sealed census heads to projection identity
ScriptedAlchemy Nov 6, 2026
c28d2c5
style(graph): format projection-bound census regression
ScriptedAlchemy Nov 6, 2026
4c5406c
style(graph): align sealed cleanup fixtures with rustfmt 2024
ScriptedAlchemy Nov 6, 2026
46b7285
fix(storage): preserve unknown sealed forms on format cleanup
ScriptedAlchemy Nov 6, 2026
5f60b30
style(graph): format sealed census regression
ScriptedAlchemy Nov 6, 2026
968c505
fix(graph): refuse unknown sealed forms in live census
ScriptedAlchemy Nov 6, 2026
65e993c
fix(graph): exclude unsupported graph formats from sealed census
ScriptedAlchemy Nov 6, 2026
6cd9a91
fix(retention): isolate unsafe unreferenced artifact entries
ScriptedAlchemy Nov 6, 2026
9e30683
style(retention): format unsafe inventory regression
ScriptedAlchemy Nov 7, 2026
81548e7
fix(graph): bound sealed receipt reads before cleanup
ScriptedAlchemy Nov 7, 2026
dd3f9c4
fix(graph): bound and atomically replace sealed proof markers
ScriptedAlchemy Nov 7, 2026
115b493
fix(graph): reuse no-follow bounded reads for sealed proof files
ScriptedAlchemy Nov 7, 2026
18e1c7f
style(graph): normalize sealed-reader helper formatting
ScriptedAlchemy Nov 7, 2026
1b6af8f
fix(retention): safely publish bounded cold-store cursors
ScriptedAlchemy Nov 7, 2026
834a4a6
fix(retention): expose bounded page progress
ScriptedAlchemy Nov 7, 2026
fd964ed
style: align bounded retention readers with rustfmt 2024
ScriptedAlchemy Nov 7, 2026
9d5693c
fix(storage): refuse symlinked footprint walk roots
ScriptedAlchemy Nov 7, 2026
7083168
style(maintenance): format symlinked footprint regression
ScriptedAlchemy Nov 7, 2026
fcce64d
fix(maintenance): reject linked cold-store cursor parents
ScriptedAlchemy Nov 7, 2026
d4098f4
fix(maintenance): anchor cold cursor access to directory handles
ScriptedAlchemy Nov 7, 2026
4158e1b
fix(maintenance): keep cursor capabilities portable
ScriptedAlchemy Nov 7, 2026
fae3278
style(maintenance): format capability-based cold cursor access
ScriptedAlchemy Nov 7, 2026
481fa49
fix(graph): preserve unowned sealed cleanup artifacts
ScriptedAlchemy Nov 7, 2026
9f1686e
fix(graph-db): refuse unrecognized sealed receipts before release
ScriptedAlchemy Nov 7, 2026
b03430d
style(graph-db): format sealed-store receipt and cleanup regressions
ScriptedAlchemy Nov 7, 2026
8bf96c6
fix(graph-db): fence sealed cleanup with held directories
ScriptedAlchemy Nov 7, 2026
f8b645c
fix(private-fs): import nofollow directory extensions
ScriptedAlchemy Nov 7, 2026
29cb821
style(storage): align nofollow helpers with rustfmt 2024
ScriptedAlchemy Nov 7, 2026
d948b4f
style(private-fs): format bounded receipt reader
ScriptedAlchemy Nov 7, 2026
da05936
fix(storage): fence sealed census under held directory handles
ScriptedAlchemy Nov 7, 2026
d1e2613
fix(graph): fence sealed proof markers under held directories
ScriptedAlchemy Nov 7, 2026
549f161
fix: handle absent sealed census ancestors and format capability scans
ScriptedAlchemy Nov 7, 2026
0566171
fix(graph-db): remove stale sealed census code after handle cutover
ScriptedAlchemy Nov 7, 2026
2f0f1ef
fix(retention): advance cold-store cursor across filtered projects
ScriptedAlchemy Nov 7, 2026
453a19b
style(retention): format cold-store cursor regression
ScriptedAlchemy Nov 7, 2026
04a4aa8
fix(projection): avoid connection-lifetime TEMP ownership sets
ScriptedAlchemy Nov 8, 2026
f7b605e
test(retention): cover multi-generation sealed replay retirement
ScriptedAlchemy Nov 8, 2026
c9ef276
style(graph-db): format sealed replay retirement test
ScriptedAlchemy Nov 8, 2026
8053819
fix(maintenance): bind cursors to the admitted profile directory
ScriptedAlchemy Nov 8, 2026
f538fd0
test(projection): verify populated cleanup and rollback
ScriptedAlchemy Nov 8, 2026
79b22c3
perf(projection): clear output through bounded ownership pages
ScriptedAlchemy Nov 8, 2026
78c610e
chore(integration): reconcile the scheduler fixture with master
ScriptedAlchemy Nov 9, 2026
6c0cf6f
chore(integration): reconcile projection recovery with master
ScriptedAlchemy Nov 9, 2026
ead1801
chore(integration): preserve committed-row SQLite lock coverage
ScriptedAlchemy Nov 9, 2026
992c5ac
Merge master into integration/application-repairs-20261105-next
ScriptedAlchemy Nov 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1227,22 +1227,106 @@ fn text_artifact_retention_refuses_tamper_and_publish_cas_movement() {

#[cfg(unix)]
#[test]
fn text_artifact_retention_refuses_symlinked_inventory_entries() {
let (store, _generations) = fixture_store(1);
fn unreferenced_nonregular_text_artifacts_do_not_block_verified_retirement() {
let (store, generations) = fixture_store(5);
let active = generations.last().expect("active generation");
let referenced = attach_fixture_text_artifact(&store, active, b"durably referenced");
let referenced_path =
code_text_artifact_path(store.path(), &referenced).expect("referenced artifact path");
let valid = text_artifact_for_bytes(&active.id, b"verified orphan beside unsafe entries");
let valid_path = write_text_artifact(&store, &valid, b"verified orphan beside unsafe entries");

let artifacts_root = code_text_artifacts_root(store.path());
std::fs::create_dir_all(&artifacts_root).expect("create artifact root");
let outside = tempfile::NamedTempFile::new().expect("outside artifact");
let symlink = artifacts_root.join(format!("text-artifact-{}.bin", "d".repeat(64)));
std::os::unix::fs::symlink(outside.path(), &symlink).expect("create symlink");
let outside = tempfile::NamedTempFile::new().expect("foreign artifact");
std::fs::write(outside.path(), b"unrelated operator data").expect("write foreign data");
let link_name = format!("text-artifact-{}.bin", "d".repeat(64));
let link = artifacts_root.join(&link_name);
std::os::unix::fs::symlink(outside.path(), &link).expect("create foreign link");
let directory_name = format!("text-artifact-{}.bin", "e".repeat(64));
let directory = artifacts_root.join(&directory_name);
std::fs::create_dir(&directory).expect("create foreign directory");
std::fs::write(directory.join("keep"), b"unrelated directory data").unwrap();

let plan = prepare_next_code_generation_retention_cancellable(
store.path(),
&BTreeSet::new(),
&|| false,
None,
)
.expect("unsafe unreferenced entries cannot veto valid retirement");
assert_eq!(plan.collectable_generations.len(), 4);
assert!(
plan.collectable_text_artifacts
.iter()
.any(|entry| entry.artifact_file == valid.artifact_file)
);
for name in [&link_name, &directory_name] {
assert!(
plan.collectable_text_artifacts
.iter()
.all(|entry| &entry.artifact_file != name),
"an untrusted entry must never become a deletion candidate"
);
}

let report = execute_code_generation_retention(
store.path(),
plan,
CodeGenerationRetentionModeV1::Apply,
UtcMicros(52),
None,
)
.expect("collect independent verified generations and orphan");
assert_eq!(report.deleted_generations.len(), 4);
assert!(!valid_path.exists(), "verified orphan was reclaimed");
assert!(
referenced_path.is_file(),
"durable referenced target remains"
);
assert!(
std::fs::symlink_metadata(&link)
.unwrap()
.file_type()
.is_symlink(),
"untrusted symlink remains untouched"
);
assert_eq!(
std::fs::read(outside.path()).unwrap(),
b"unrelated operator data"
);
assert_eq!(
std::fs::read(directory.join("keep")).unwrap(),
b"unrelated directory data"
);
}

#[cfg(unix)]
#[test]
fn referenced_text_artifact_symlink_still_refuses_retention() {
let (store, generations) = fixture_store(1);
let active = generations.last().expect("active generation");
let referenced = attach_fixture_text_artifact(&store, active, b"durably referenced");
let target = code_text_artifact_path(store.path(), &referenced).unwrap();
let outside = tempfile::NamedTempFile::new().expect("foreign artifact");
std::fs::write(outside.path(), b"unrelated operator data").unwrap();
std::fs::remove_file(&target).unwrap();
std::os::unix::fs::symlink(outside.path(), &target).unwrap();

let error = plan_code_generation_retention(store.path(), &BTreeSet::new())
.expect_err("symlinked artifact entry must fail closed");
.expect_err("a referenced descriptor cannot be replaced by a symlink");
assert!(matches!(
error,
CodeGenerationRetentionErrorV1::UnsafeState(_)
));
assert!(
symlink.exists(),
"refusal must preserve foreign link evidence"
std::fs::symlink_metadata(&target)
.unwrap()
.file_type()
.is_symlink()
);
assert_eq!(
std::fs::read(outside.path()).unwrap(),
b"unrelated operator data"
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,10 +451,16 @@ pub(super) fn plan_collectable_text_artifacts_budgeted_page_cancellable(
Err(error) => return Err(storage(error)),
};
if !metadata.file_type().is_file() {
return Err(CodeGenerationRetentionErrorV1::UnsafeState(format!(
"code text artifact inventory path '{}' is not a regular file",
path.display()
)));
// Referenced descriptors were verified above and remain fail-closed.
// Unreferenced symlinks/directories have no deletion authority,
// but must not veto a separate verified artifact or generation.
tracing::warn!(
event = "code_text_artifact_retention_candidate_preserved",
artifact = %path.display(),
reason = "non_regular_inventory_entry",
"unreferenced non-regular artifact preserved during retention"
);
continue;
}
let staging_source = staging_text_artifact_source_digest(&file_name)
.or_else(|| staging_sidecar_text_artifact_source_digest(&file_name));
Expand Down
13 changes: 9 additions & 4 deletions crates/tracedecay-daemon-service/src/doctor_kernel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -825,11 +825,11 @@ pub async fn collect_code_generation_retention_findings(
/// absent table on a store that never published, a lock, a corrupt row).
async fn live_sealed_generations(
graph: &tracedecay_runtime_core::db::Database,
) -> Option<BTreeSet<String>> {
) -> Option<BTreeSet<(String, String, String)>> {
let mut rows = graph
.read_connection()
.query(
"SELECT replay.generation
"SELECT replay.namespace, replay.projection, replay.generation
FROM graph_publication_replay_v1 AS replay
LEFT JOIN graph_verified_heads_v1 AS head
ON head.shard_id = replay.shard_id
Expand All @@ -838,14 +838,19 @@ async fn live_sealed_generations(
WHERE head.replay_sequence IS NULL
OR replay.sequence >= head.replay_sequence
UNION
SELECT generation FROM graph_publication_replay_dependencies_v1",
SELECT namespace, projection, generation
FROM graph_publication_replay_dependencies_v1",
(),
)
.await
.ok()?;
let mut heads = BTreeSet::new();
while let Some(row) = rows.next().await.ok()? {
heads.insert(row.get::<String>(0).ok()?);
heads.insert((
row.get::<String>(0).ok()?,
row.get::<String>(1).ok()?,
row.get::<String>(2).ok()?,
));
}
Some(heads)
}
Expand Down
28 changes: 21 additions & 7 deletions crates/tracedecay-global-db/src/observation/canonical_body_gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pub const MAX_CANONICAL_BODY_GC_ROWS: usize = 48;
pub struct CanonicalBodyGcReport {
pub roots_scanned: u64,
pub bodies_scanned: u64,
/// Candidates discharged in the committed page, even if their body stays live.
pub candidates_discharged: u64,
pub bodies_deleted: u64,
/// Stored BLOB bytes deleted, including the effect of their existing
/// compression. This is not database-file or filesystem space reclaimed.
Expand Down Expand Up @@ -323,13 +325,25 @@ async fn collect_candidates(
drop(deleted);
// Referenced candidates are discharged too. The final owner removal
// records them again; idle ticks never revisit every retained body.
conn.execute(
"DELETE FROM canonical_body_gc_candidates
WHERE content_hash IN (SELECT value FROM json_each(?1))",
params![hashes],
)
.await
.map_err(db_error)?;
// Deleting an unreferenced body cascades to its candidate row. The
// remaining explicit removals discharge referenced bodies that stay live.
let explicitly_discharged = conn
.execute(
"DELETE FROM canonical_body_gc_candidates
WHERE content_hash IN (SELECT value FROM json_each(?1))",
params![hashes],
)
.await
.map_err(db_error)?;
let discharged = explicitly_discharged
.checked_add(report.bodies_deleted)
.ok_or_else(|| integrity_error("canonical body candidate discharge overflow"))?;
if discharged != report.bodies_scanned {
return Err(integrity_error(
"canonical body candidate page was not fully discharged",
));
}
report.candidates_discharged = discharged;
Ok(())
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -506,3 +506,52 @@ async fn nonpositive_legacy_owner_ids_cannot_be_skipped_by_bootstrap() {
);
}
}

#[asupersync::test]
async fn zero_delete_pages_discharge_distinct_candidates_and_quiesce() {
let store = Store::open().await;
for ordinal in 0..52 {
let body =
StoredCanonicalBody::pack(format!("still referenced {ordinal}").as_bytes()).unwrap();
insert_body(store.conn(), &body).await;
store
.conn()
.execute(
"INSERT INTO lcm_raw_messages(storage_kind, content, content_hash)
VALUES ('inline', NULL, ?1)",
params![body.content_hash.as_str()],
)
.await
.unwrap();
}
store.install().await;

let mut discharged_pages = Vec::new();
for _ in 0..16 {
let report = store.tick(48).await;
assert_eq!(report.bodies_deleted, 0, "referenced bodies stay live");
if report.candidates_discharged > 0 {
assert_eq!(report.candidates_discharged, report.bodies_scanned);
discharged_pages.push(report.candidates_discharged);
}
if !report.has_more {
break;
}
}
assert_eq!(discharged_pages, vec![48, 4]);
assert!(!has_candidates(store.conn()).await.unwrap());
assert_eq!(
store.tick(48).await,
CanonicalBodyGcReport {
bootstrap_complete: true,
..CanonicalBodyGcReport::default()
}
);
let mut rows = store
.conn()
.query("SELECT COUNT(*) FROM session_canonical_bodies", ())
.await
.unwrap();
let count: i64 = rows.next().await.unwrap().unwrap().get(0).unwrap();
assert_eq!(count, 52);
}
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ fn collision_candidate_at(
(observation, anchored)
}

fn collision_candidate(
pub(super) fn collision_candidate(
session_id: &SessionId,
record_id: &str,
generation: u64,
Expand Down
Loading
Loading