Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## Unreleased

- Enforce `COMPASS_MAX_GRAPH_BYTES` against the actual canonical bytes streamed
into atomic staging for full, SQLite-backed, and fact-neutral delta
publications. A real overrun leaves the previous graph and store reference
active. Add a five-estate qualification command and measured expansion
distribution; source-to-graph ratios remain diagnostic evidence and are not
an admission rule.

- Reset all 14 registered universal-evidence producer versions to v1 and keep
them `Qualified` under the refreshed release decision at
`tests/qualification/universal-evidence-promotion.json`. Cached evidence
Expand Down
48 changes: 48 additions & 0 deletions PERFORMANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,54 @@ Compare a proposed change with a previously approved Compass result captured on
the same runner and corpus. A median regression above 10% requires explicit
review and evidence explaining the tradeoff.

## Canonical graph size qualification

`COMPASS_MAX_GRAPH_BYTES` is enforced against the canonical JSON bytes emitted
into atomic staging. Compass does not predict canonical graph size from source
bytes: measured expansion varies with language, structural density, inventory
coverage, and extraction evidence, so a single linear multiplier is not a safe
admission model. If the emitted stream crosses the bound, publication aborts
and the previously active artifact set remains intact.

Measure the five-estate corpus after producing one completed `graph.json` per
estate. The command reads only the bounded metadata prefix, sums the persisted
`byteSize` values for admitted files, and reports per-estate ratios plus the
minimum, median, and maximum distribution:

```bash
python3 scripts/qualify_graph_size_ratios.py \
--estate ara-scanworks-ui /path/to/ara-scanworks-ui/compass-out/graph.json \
--estate ara-pm /path/to/ara-pm/compass-out/graph.json \
--estate captivebrowser-133 /path/to/captivebrowser-133/compass-out/graph.json \
--estate venus /path/to/venus/compass-out/graph.json \
--estate solaris-platform /path/to/solaris-platform/compass-out/graph.json \
--json-output target/qualification/graph-size-ratios.json \
--markdown-output target/qualification/graph-size-ratios.md
```

The report schema is `compass.qualification.graph-size-ratios/1`. Generated
reports stay under `target/`; a retained observation must identify the Compass
commit, corpus revision, and host separately. Ratios are sizing evidence only
and must never be converted into a fatal preflight estimate.

The 2026-08-30 qualification used the release binary from this change, JSON
storage, and identical `--no-viz --no-cluster --no-program` settings for every
estate. Admitted source bytes are the persisted `byteSize` values in each
completed artifact, not a filesystem-wide estimate:

| Estate | Admitted files | Admitted source bytes | Canonical graph bytes | Ratio |
| --- | ---: | ---: | ---: | ---: |
| `ara-pm` | 952 | 39,468,272 | 14,274,716 | 0.361676× |
| `ara-scanworks-ui` | 494 | 6,688,344 | 3,721,796 | 0.556460× |
| `captivebrowser-133` | 1,602 | 52,006,357 | 403,028,839 | 7.749607× |
| `solaris-platform` | 5,102 | 112,938,563 | 87,429,961 | 0.774137× |
| `venus` | 2,410 | 140,482,350 | 6,832,605 | 0.048637× |

The observed distribution is **0.048637× minimum**, **0.556460× median**, and
**7.749607× maximum**. The roughly 159-fold spread between the minimum and
maximum is direct evidence that source bytes alone are not a safe fatal
admission signal.

## Markdown graph-v1 quality qualification

Markdown tables intentionally retain their table, header, row, and cell nodes.
Expand Down
127 changes: 127 additions & 0 deletions crates/compass-cli/tests/canonical_publication_limit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use std::error::Error;
use std::fs;
use std::process::Command;

use compass_files::BuildGuard;

#[test]
fn actual_delta_overrun_preserves_the_active_snapshot() -> Result<(), Box<dyn Error>> {
let root = tempfile::tempdir()?;
let source = root.path().join("sample.rs");
fs::write(&source, "pub fn sample() -> u64 { 1 }\n// before\n")?;
let initial = Command::new(env!("CARGO_BIN_EXE_compass"))
.args([
"update",
".",
"--force",
"--store",
"json",
"--no-cluster",
"--no-viz",
"--no-program",
])
.current_dir(root.path())
.env_remove("COMPASS_OUT")
.env_remove("COMPASS_MAX_GRAPH_BYTES")
.output()?;
assert!(
initial.status.success(),
"initial publication failed: {}",
String::from_utf8_lossy(&initial.stderr)
);

let output = root.path().join("compass-out");
let active_graph = BuildGuard::resolve_artifact(&output, "graph.json")?;
let prior_bytes = fs::read(&active_graph)?;
fs::write(
&source,
format!(
"pub fn sample() -> u64 {{ 1 }}\n// {}\n",
"after".repeat(200)
),
)?;

let rejected = Command::new(env!("CARGO_BIN_EXE_compass"))
.args([
"update",
".",
"--store",
"json",
"--no-cluster",
"--no-viz",
"--no-program",
])
.current_dir(root.path())
.env_remove("COMPASS_OUT")
.env("COMPASS_MAX_GRAPH_BYTES", prior_bytes.len().to_string())
.output()?;
assert_eq!(rejected.status.code(), Some(1));
let stderr = String::from_utf8(rejected.stderr)?;
assert!(stderr.contains("canonical graph exceeds"), "{stderr}");
assert!(stderr.contains("COMPASS_MAX_GRAPH_BYTES"), "{stderr}");

let still_active = BuildGuard::resolve_artifact(&output, "graph.json")?;
assert_eq!(fs::read(still_active)?, prior_bytes);
Ok(())
}

#[test]
fn actual_full_overrun_preserves_the_active_sqlite_snapshot() -> Result<(), Box<dyn Error>> {
let root = tempfile::tempdir()?;
let source = root.path().join("sample.rs");
fs::write(&source, "pub fn sample() -> u64 { 1 }\n")?;
let initial = Command::new(env!("CARGO_BIN_EXE_compass"))
.args([
"update",
".",
"--force",
"--store",
"sqlite",
"--no-cluster",
"--no-viz",
"--no-program",
])
.current_dir(root.path())
.env_remove("COMPASS_OUT")
.env_remove("COMPASS_MAX_GRAPH_BYTES")
.output()?;
assert!(
initial.status.success(),
"initial SQLite publication failed: {}",
String::from_utf8_lossy(&initial.stderr)
);

let output = root.path().join("compass-out");
let active_graph = BuildGuard::resolve_artifact(&output, "graph.json")?;
let prior_graph = fs::read(&active_graph)?;
let active_store_ref = BuildGuard::resolve_artifact(&output, "store.ref")?;
let prior_store_ref = fs::read(&active_store_ref)?;
let expanded = (0..200)
.map(|index| format!("pub fn added_{index}() -> usize {{ {index} }}\n"))
.collect::<String>();
fs::write(&source, expanded)?;

let rejected = Command::new(env!("CARGO_BIN_EXE_compass"))
.args([
"update",
".",
"--store",
"sqlite",
"--no-cluster",
"--no-viz",
"--no-program",
])
.current_dir(root.path())
.env_remove("COMPASS_OUT")
.env("COMPASS_MAX_GRAPH_BYTES", prior_graph.len().to_string())
.output()?;
assert_eq!(rejected.status.code(), Some(1));
let stderr = String::from_utf8(rejected.stderr)?;
assert!(stderr.contains("canonical graph exceeds"), "{stderr}");

let still_active_graph = BuildGuard::resolve_artifact(&output, "graph.json")?;
let still_active_ref = BuildGuard::resolve_artifact(&output, "store.ref")?;
assert_eq!(fs::read(still_active_graph)?, prior_graph);
assert_eq!(fs::read(still_active_ref)?, prior_store_ref);
Ok(())
}
13 changes: 7 additions & 6 deletions crates/compass-core/src/cluster_existing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ use std::time::{Duration, Instant};
use compass_files::{BuildGuard, write_atomic_with_digest, write_json_atomic, write_text_atomic};
use compass_graph::{
ClusterOptions, Communities, GodNode, blind_spot_report, cluster, community_member_signatures,
god_nodes, label_communities_by_hub, remap_communities_to_previous, score_communities,
suggest_questions, surprising_connections, write_canonical_graph_json,
god_nodes, label_communities_by_hub, max_canonical_graph_bytes, remap_communities_to_previous,
score_communities, suggest_questions, surprising_connections,
write_canonical_graph_json_bounded,
};
use compass_model::GraphDocument;
use compass_model::GraphError;
Expand Down Expand Up @@ -280,12 +281,12 @@ where
let graph_path = staging.join("graph.json");
let graph_identity = if let Some(typed) = typed_document {
let receipt = write_atomic_with_digest(&graph_path, |writer| {
write_canonical_graph_json(&typed, writer).map_err(|source| {
compass_files::FileError::Io {
write_canonical_graph_json_bounded(&typed, writer, max_canonical_graph_bytes()).map_err(
|source| compass_files::FileError::Io {
path: graph_path.clone(),
source,
}
})
},
)
})?;
format!("sha256:{}", receipt.sha256)
} else {
Expand Down
65 changes: 37 additions & 28 deletions crates/compass-core/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ use compass_graph::{
build_owned_with_tiebreaker_at_inference as build_document, canonical_edge_kind,
canonical_raw_edge_sites, cluster_incremental, deduped_node_count, extraction_from_v1,
garbage_collect_graph_snapshots, graph_insights_with_blind_spots, graph_snapshot_needs_gc,
label_communities_by_hub, normalize_document_v1_with_evidence_best_effort_owned_at_inference,
label_communities_by_hub, max_canonical_graph_bytes,
normalize_document_v1_with_evidence_best_effort_owned_at_inference,
normalize_document_v1_with_inventory_and_source_digests_best_effort_owned_at_inference,
normalize_document_v1_with_inventory_best_effort_at_inference, score_communities,
write_canonical_graph_json, write_fact_neutral_graph_json_delta_prevalidated,
write_canonical_graph_json_bounded, write_fact_neutral_graph_json_delta_prevalidated_bounded,
};
use compass_languages::{
BindingFact, DeclarationFact, EXTRACTION_QUALITY_EXTENSION, EXTRACTION_QUALITY_PARTIAL,
Expand Down Expand Up @@ -1974,11 +1975,12 @@ fn publish_fact_neutral_incremental(
let receipt = write_atomic_with_digest(&graph_path, |writer| {
let delta_started = Instant::now();
let used_delta = if let Some(bytes) = previous_bytes.as_deref() {
write_fact_neutral_graph_json_delta_prevalidated(
write_fact_neutral_graph_json_delta_prevalidated_bounded(
bytes,
current,
changed_node_ids,
writer,
max_canonical_graph_bytes(),
)
.map_err(|source| compass_files::FileError::Io {
path: graph_path.clone(),
Expand All @@ -1998,12 +2000,11 @@ fn publish_fact_neutral_incremental(
if used_delta {
Ok(())
} else {
write_canonical_graph_json(current, writer).map_err(|source| {
compass_files::FileError::Io {
write_canonical_graph_json_bounded(current, writer, max_canonical_graph_bytes())
.map_err(|source| compass_files::FileError::Io {
path: graph_path.clone(),
source,
}
})
})
}
})?;
(
Expand Down Expand Up @@ -3629,11 +3630,14 @@ fn build_graph_inner_unscoped(
} else {
let graph_path = output_dir.join("graph.json");
let receipt = write_atomic_with_digest(&graph_path, |writer| {
write_canonical_graph_json(&published.document, writer).map_err(|source| {
compass_files::FileError::Io {
path: graph_path.clone(),
source,
}
write_canonical_graph_json_bounded(
&published.document,
writer,
max_canonical_graph_bytes(),
)
.map_err(|source| compass_files::FileError::Io {
path: graph_path.clone(),
source,
})
})?;
(
Expand Down Expand Up @@ -4192,11 +4196,14 @@ fn build_graph_inner_unscoped(
} else {
let graph_path = output_dir.join("graph.json");
let receipt = write_atomic_with_digest(&graph_path, |writer| {
write_canonical_graph_json(&published_document, writer).map_err(|source| {
compass_files::FileError::Io {
path: graph_path.clone(),
source,
}
write_canonical_graph_json_bounded(
&published_document,
writer,
max_canonical_graph_bytes(),
)
.map_err(|source| compass_files::FileError::Io {
path: graph_path.clone(),
source,
})
})?;
(
Expand Down Expand Up @@ -4879,12 +4886,11 @@ fn publish_graph_and_store_from_canonical(
let (graph_receipt, content) = rayon::join(
|| {
write_atomic_with_digest(&graph_path, |writer| {
write_canonical_graph_json(graph, writer).map_err(|source| {
compass_files::FileError::Io {
write_canonical_graph_json_bounded(graph, writer, max_canonical_graph_bytes())
.map_err(|source| compass_files::FileError::Io {
path: graph_path.clone(),
source,
}
})
})
})
},
|| builder.prepare_content(&store, graph),
Expand Down Expand Up @@ -4922,8 +4928,12 @@ fn publish_graph_and_store_delta(
let result = write_atomic_with_digest(&graph_path, |writer| {
let used_delta = match (previous_bytes.as_deref(), changed_node_ids) {
(Some(bytes), Some(changed)) => {
write_fact_neutral_graph_json_delta_prevalidated(
bytes, graph, changed, writer,
write_fact_neutral_graph_json_delta_prevalidated_bounded(
bytes,
graph,
changed,
writer,
max_canonical_graph_bytes(),
)
.map_err(|source| {
compass_files::FileError::Io {
Expand All @@ -4937,12 +4947,11 @@ fn publish_graph_and_store_delta(
if used_delta {
Ok(())
} else {
write_canonical_graph_json(graph, writer).map_err(|source| {
compass_files::FileError::Io {
write_canonical_graph_json_bounded(graph, writer, max_canonical_graph_bytes())
.map_err(|source| compass_files::FileError::Io {
path: graph_path.clone(),
source,
}
})
})
}
});
profile_internal_duration("graph JSON delta publication", started.elapsed());
Expand Down Expand Up @@ -8773,7 +8782,7 @@ mod tests {

let changed_graph = V1GraphDocument::load(&changed.output_dir.join("graph.json"))?;
let mut canonical_changed = Vec::new();
write_canonical_graph_json(&changed_graph, &mut canonical_changed)?;
compass_graph::write_canonical_graph_json(&changed_graph, &mut canonical_changed)?;
assert_eq!(
fs::read(changed.output_dir.join("graph.json"))?,
canonical_changed,
Expand Down
8 changes: 5 additions & 3 deletions crates/compass-graph/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ pub use snapshot::{
GRAPH_SNAPSHOT_MAX_ITEMS, GRAPH_SNAPSHOT_MAX_OBJECTS, GRAPH_SNAPSHOT_SELECTOR_SCHEMA_V1,
GRAPH_TERM_POSTING_CHUNK_ITEMS, GraphSnapshotBuilder, GraphSnapshotGcStats,
GraphSnapshotManifest, GraphSnapshotMetadata, GraphSnapshotReader, IndexKind,
PreparedGraphSnapshot, PreparedGraphSnapshotContent, SnapshotError, SnapshotReadLimits,
SnapshotRoot, SnapshotSelector, TermPostingWork, activate_graph_snapshot,
MAX_CANONICAL_GRAPH_BYTES, PreparedGraphSnapshot, PreparedGraphSnapshotContent, SnapshotError,
SnapshotReadLimits, SnapshotRoot, SnapshotSelector, TermPostingWork, activate_graph_snapshot,
active_graph_snapshot, canonical_graph_document, canonical_graph_document_presorted,
canonical_graph_json, encode_graph_index_key, garbage_collect_graph_snapshots,
graph_snapshot_needs_gc, prepare_graph_snapshot, write_canonical_graph_json,
graph_snapshot_needs_gc, max_canonical_graph_bytes, prepare_graph_snapshot,
write_canonical_graph_json, write_canonical_graph_json_bounded,
write_fact_neutral_graph_json_delta, write_fact_neutral_graph_json_delta_prevalidated,
write_fact_neutral_graph_json_delta_prevalidated_bounded,
};
pub use v1::{
BuildEvidence, InventoryEvidence, SourceDigest, V1_PUBLICATION_SEMANTICS_VERSION,
Expand Down
Loading