Skip to content
Merged
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
32 changes: 26 additions & 6 deletions .github/scripts/landing-constants-guard.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,17 @@ if [ -z "${VERSION:-}" ]; then
VERSION="$(git tag --list 'v1.0.0-rc.*' 2>/dev/null | sort -V | tail -1 | sed 's/^v//')"
fi

# The tree's own version (engine/Cargo.toml). A release PR stamps the footers
# with the version it is cutting BEFORE that version's tag exists, so requiring
# tag-equality on a release branch is unsatisfiable by construction — rc.76
# dodged it only by cutting directly on main, which skipped every release gate
# (#474). A footer matching the version of the tree it ships in is
# self-consistent, not drift; the stale-footer drift this check exists for is a
# footer matching NEITHER the tag NOR the tree. The allowed set is therefore
# {newest released tag, engine/Cargo.toml version} — which collapses to just
# the tag on every non-release branch.
TREE_VERSION="$(awk -F'"' '/^version = /{print $2; exit}' engine/Cargo.toml 2>/dev/null || true)"

fails=0
note() { printf ' %s\n' "$1"; }
fail() { printf 'FAIL %s\n' "$1"; fails=$((fails + 1)); }
Expand Down Expand Up @@ -114,15 +125,20 @@ rm -f /tmp/.lcg_conf.$$
# falsify a real run. Only the footer claims "this is the current release".
if [ -n "${VERSION:-}" ]; then
vre="$(echo "$VERSION" | sed 's/\./\\./g')"
tre="$(printf '%s' "$TREE_VERSION" | sed 's/\./\\./g')"
allow_ver="V${vre}([^0-9]|$)"
if [ -n "$tre" ] && [ "$tre" != "$vre" ]; then
allow_ver="V(${vre}|${tre})([^0-9]|$)"
fi
bad_ver="$(grep -rniE 'XERJ\.AI[^<]*V1\.0\.0-RC\.?[0-9]+' "$LANDING" --include='*.html' 2>/dev/null \
| grep -viE "V${vre}([^0-9]|$)" \
| grep -viE "$allow_ver" \
| grep -v '<!-- snapshot:' || true)"
if [ -n "$bad_ver" ]; then
fail "version stamps disagree with the newest released tag (v$VERSION)"
fail "version stamps disagree with the newest released tag (v$VERSION) and the tree's own version (v$TREE_VERSION)"
echo "$bad_ver" | head -20 | while IFS= read -r l; do note "${l:0:160}"; done
n="$(echo "$bad_ver" | wc -l)"; [ "$n" -gt 20 ] && note "... and $((n - 20)) more"
else
pass "version stamps match v$VERSION"
pass "version stamps match v$VERSION (tree: v$TREE_VERSION)"
fi

# The agent-facing llms*.txt carry a prose "Current release: **vX.Y.Z**"
Expand All @@ -131,14 +147,18 @@ if [ -n "${VERSION:-}" ]; then
# shipped) exactly the way the footers used to — an agent that reads llms.txt
# is handed the wrong version (#515 / #520). Hold it to the newest tag too.
# Snapshot-marked lines stay exempt.
allow_llms="v${vre}([^0-9]|$)"
if [ -n "$tre" ] && [ "$tre" != "$vre" ]; then
allow_llms="v(${vre}|${tre})([^0-9]|$)"
fi
bad_llms="$(grep -rniE 'current release:[^<]*v1\.0\.0-rc\.?[0-9]+' "$LANDING" --include='llms*.txt' 2>/dev/null \
| grep -viE "v${vre}([^0-9]|$)" \
| grep -viE "$allow_llms" \
| grep -v '<!-- snapshot:' || true)"
if [ -n "$bad_llms" ]; then
fail "llms*.txt 'Current release' disagrees with the newest released tag (v$VERSION)"
fail "llms*.txt 'Current release' disagrees with the newest released tag (v$VERSION) and the tree's own version (v$TREE_VERSION)"
echo "$bad_llms" | while IFS= read -r l; do note "${l:0:160}"; done
else
pass "llms 'Current release' matches v$VERSION"
pass "llms 'Current release' matches v$VERSION (tree: v$TREE_VERSION)"
fi
else
note "skipped version check: no v1.0.0-rc.* tag found"
Expand Down
68 changes: 68 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,35 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.0.0-rc.77] - 2026-09-21

The stateless-index and reader-fairness release. Two headline changes:
`storage.backend = "s3"` starts for real (#965), and readers stop starving
under sustained ingest (#1013) — both with fail-before reproductions.

### Added

- **`storage.backend = "s3"` works (#965, PR #1008).** Before this commit the
startup guard refused the setting outright, honestly: a flush uploaded one
file per segment while a 25-field segment family is 104 files, `snapshot.json`
never left local disk, and a fresh node pointed at the bucket saw zero
segments. Now the whole segment family packs into ONE immutable object — a
ZBM1 bundle whose footer is the existing ZCM1 flush-completion manifest
extended with absolute offsets (trailer shape adapted from quickwit's
split-footer bundle, Apache-2.0, approach only). One object per segment plus
the catalogue PUT is ~18% of Cloudflare R2's free Class-A tier at a 30 s
flush interval, where 104 PUTs would be ~899%. `snapshot.json` per index is
the publication point: merges publish output bundles before retiring inputs,
boot fetches the catalogue and adopts backfill, reads hydrate families on
demand, per-index meta rides the same bucket. Config grows to 128 settings
(`storage.s3_bucket`); an empty bucket name stays a hard error (XERJ never
calls CreateBucket). Fail-before, verified live on the base commit:
`an_s3_index_round_trips_to_a_fresh_node` panicked "the flush must publish
the snapshot catalogue". Honest limits stay documented in
`docs/OBJECT_STORAGE.md`: single-writer v1, the WAL stays local, in-memory
packs with no multipart upload, crash-consistency pinned against the
in-process simulation only.

### Fixed

- **`/v1/systemone` votes on the payload, never on the instruction prose.**
Expand Down Expand Up @@ -34,6 +63,45 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
client entry points (neutral-query gap 0.8542) and under a deliberately
spammy query (gap 0.6087, threshold 0.3) — client 0.1.2, unmodified.

- **Readers no longer starve under sustained ingest (#1013, PR #1014).**
`CollectionPublication` is a seqlock; `search()` admitted a reader and
validated the token at the **end** of the search — demanding a writer-free
*interval* spanning the whole scan. The flush path holds its guard across
the entire finalize, concurrent flushes are allowed, and every document
write takes its own bracket, so under sustained ingest writer-free
search-length intervals stop existing: search and `delete_by_query` retried
to their deadline and returned HTTP 500 (reproduced twice in the #950
profiling run). Validation now happens at the **capture boundary**: a
generation reference before the first collection-state read, validated with
the seqlock evenness check right after the memtable snapshot — readers need
a writer-free *instant*, not a writer-free search. A straddled capture
returns a typed retry root (`CollectionCaptureCrossedPublication`,
downcastable through `anyhow` context wraps) and `search()` retries until
its deadline. Fail-before (`search_completes_under_publication_churn`): a
churn thread holds a cancelled 1 ms publication bracket every 3 ms — old
code FAILED at its 2 s deadline with exactly the production error; new code
passes in 0.37 s, total exactly 5050. Gates: 723/723 engine lib tests,
ES-YAML 1376/0 failed. Follow-up #1015 tracks shrinking the flush bracket
itself (a latency consideration now, not a starvation hazard).

- **The console tour's pill assertions raced the route hand-off (#1011).**
The browser security suite's policy tour waited on
`aria-busy === 'false'`, but app.js only ever sets aria-busy to 'false' at
the END of a render — nothing flips it 'true' while the next route's query
is pending — so the wait was satisfied by the previous route's still-mounted
DOM, and the fixed 150 ms sleep then read the old view's pill. Failed only
on slow runners (PR #1008's CI): the window scales with runner latency. The
tour now waits for the route's own scene, and a latency-injection
demonstration failed the old assertion deterministically at 400 ms.

- **CI: the reference-coding toolkit's tests never ran.** `cargo test
--features x -- xc_ y` passes one filter per invocation, so `xc_` matched
nothing and the step was green on zero tests (3aba5786). And the toolkit
job's MCP schema gate looked for a release binary that job never builds
(8f8e5444). Both fixed; the job now genuinely executes.

- **docs: roadmap re-reviewed against rc.76** (release-drift guard, 213ce8f8).

## [1.0.0-rc.76] - 2026-09-21

The JEV-interface and honest-fixes release. The headline is a measured answer
Expand Down
84 changes: 40 additions & 44 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This roadmap tracks capabilities that are **planned but not yet fully implemented**, so the project's public claims stay honest about what ships today versus what is coming. Status is verified against the actual code and by real API requests to the release binary, not aspirational.

Last reviewed: 2026-09-21 (against `v1.0.0-rc.76` and `main`). Statuses trace to issues, merged PRs, the CHANGELOG, and the conformance suite; items carried forward from the 2026-07-12 review without fresh live verification are marked as such. This review line is machine-checked: `docs_capability_lists` fails the build if a release is cut without re-reviewing this file (issue #298). *The zero-token direction* below was added and verified separately on 2026-09-18, against `main` @ `4d8dadbf`; the rest of the file was not re-reviewed on that date.
Last reviewed: 2026-09-21 (against `v1.0.0-rc.77` and `main`). Statuses trace to issues, merged PRs, the CHANGELOG, and the conformance suite; items carried forward from the 2026-07-12 review without fresh live verification are marked as such. This review line is machine-checked: `docs_capability_lists` fails the build if a release is cut without re-reviewing this file (issue #298). This review rolled the *Next release* section and the open-defects shortlist forward against the rc.77 cut; the *Shipping today* claims were last live-verified against rc.76, and *The zero-token direction* below was verified separately on 2026-09-18, against `main` @ `4d8dadbf`.

## Follow the roadmap

Expand Down Expand Up @@ -31,49 +31,45 @@ These are implemented and exercised by real API requests / the test suite / benc

The release-by-release record of how all of this landed is [CHANGELOG.md](./CHANGELOG.md) — this file no longer duplicates it. Be aware of a real gap in that record: rc.1–rc.18 and rc.71 have entries, **rc.19 through rc.70 do not**. Those 52 releases are reconstructable only from `git log` and the release list, and closing that gap is itself a GA item below.

## Next release — [v1.0.0-rc.77](https://github.com/xerj-org/xerj/milestones)

**rc.76 was cut on 2026-09-21** — its full contents are the
[CHANGELOG.md](./CHANGELOG.md) entry, not this file. It is **the JEV-interface and
honest-fixes release**: the node answers the System One wire itself — `POST
/v1/systemone` (native REST) and `POST /_decide` (ES-compat), a rank-weighted kNN vote
over a labelled-history index, nothing leaving the node — and the unmodified pip
`jev-reranker` runs against it through both of its entry points. Two deliberate,
test-pinned wire breaks: `model` echoes `xerj-history-vote-1` (never a Jev name), and
zero support is a 422 (never a fabricated 0.5). The same release ships a WAND fast path
in `xerj-fts` (2.0× on the vote workload, bit-identical scores), `xerj code` /
`xerj corpus add|index|list` (the reference-coding loop as binary commands), FiQA and
BEIR judged-query datasets on the record, .deb release assets (amd64/arm64), and a long
list of fixes each carrying its own reproduction — the rerank stage as it shipped in
rc.75 scored 0.3822 nDCG@10 where plain BM25 scored 0.7750, and that bug was ours.

**In flight for rc.77:**

- **The systemone vote-text fix** — instruction wording must not change the answer and
object state must not be silently dropped
([#1000](https://github.com/xerj-org/xerj/issues/1000),
[#1001](https://github.com/xerj-org/xerj/issues/1001); PR
[#1007](https://github.com/xerj-org/xerj/pull/1007)): measured 32- and 79-point
accuracy swings from wording and key names, both closed by voting on exactly what the
question points at.
- **Segments in an object store** ([#965](https://github.com/xerj-org/xerj/issues/965)):
the ZBM1 bundle format and the flush/merge/read/boot wiring exist on the branch; the
gates and the flip are what remains.
- **RSS feeds as an autoindex source** ([#950](https://github.com/xerj-org/xerj/issues/950)),
evaluated against the ledger evidence #948 landed for ingest memory.
- **Retrieval quality** — the symbol index returns whole class and method bodies rather
than declarations, measured at 32-48x more bytes than grep for the same answer
([#500](https://github.com/xerj-org/xerj/issues/500)). This one undercuts a claim the
project leads with, so it is a correctness issue about our own marketing as much as a
performance one.
- **CI reliability** — [#751](https://github.com/xerj-org/xerj/issues/751) still hangs the
default-parallelism test step intermittently, and it was red on `main` repeatedly during
the rc.72 cut. The cost is not the failed run, it is that a red gate stops distinguishing
a real break from noise. [#891](https://github.com/xerj-org/xerj/issues/891) is a
confirmed instance of the same class (a process-global counter two tests share).
- **Data-loss follow-up** — [#890](https://github.com/xerj-org/xerj/issues/890): the
prefix-scoped exclusion sweep can over-delete across corpora on a legacy text-mapped
catalog. Filed against code that shipped in rc.72.
## Next release — [v1.0.0-rc.78](https://github.com/xerj-org/xerj/milestones)

**rc.77 was cut on 2026-09-21** — its full contents are the
[CHANGELOG.md](./CHANGELOG.md) entry, not this file. It is **the stateless-index and
reader-fairness release**: `storage.backend = "s3"` works for real (#965 — one immutable
ZBM1 bundle per segment instead of 104 PUTs, `snapshot.json` as the publication point,
merges publish before retiring inputs, a fresh node adopts the bucket), and readers stop
starving under sustained ingest (#1013 — the seqlock publish bracket no longer spans the
flush build; the evenness check that catches a straddling capture is part of what
shipped). The same release carries the systemone vote-text fixes (#1000/#1001: the vote
text is exactly what the question points at, never the instruction prose — a measured
32-point accuracy swing from wording alone), the console-tour pill race (#1011), and CI
repairs (the reference-coding toolkit's tests now actually run).

**In flight for rc.78:**

- **Flush bracket, reader fairness part two**
([#1015](https://github.com/xerj-org/xerj/issues/1015)): freeze the shard into a
frozen-generation list *before* the segment build, so ingest keeps writing while the
flush serializes, and retire the frozen generation inside the publish bracket. The
#1014 evenness check rejects captures that straddle a retire, so the bracket itself
must stay ms-scale — that constraint is the design.
- **Resident-set memory growth during large corpus ingest**
([#950](https://github.com/xerj-org/xerj/issues/950)): 27.1 GB peak RSS against a
15 GB breaker across 5,369 indices, with the breaker engaged 44 times yet never
capping the peak; idle steady-state ~4.5 GB. Both ingest aborts trace to #1013 (and
its fix has shipped), so what remains is attributing the growth itself — a third
full-scale measurement run and a symbolized heap profile are under way.

**Open defects carried into rc.78.**
[#1015](https://github.com/xerj-org/xerj/issues/1015) (the flush publication guard
still spans the multi-second segment build — the remaining reader-fairness gap) and
[#950](https://github.com/xerj-org/xerj/issues/950) (ingest RSS exceeds the breaker
without the breaker capping it) are the open defects with code consequences.
Trackers [#941](https://github.com/xerj-org/xerj/issues/941) (zero-token direction),
[#874](https://github.com/xerj-org/xerj/issues/874) and
[#298](https://github.com/xerj-org/xerj/issues/298) (this file's own review cadence)
stay open by design; everything else the rc.77 cut closed is recorded in the
CHANGELOG, not here.

## The road to [v1.0.0 GA](https://github.com/xerj-org/xerj/milestone/2)

Expand Down
34 changes: 17 additions & 17 deletions engine/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading