feat: make the docs index build deterministic and change-aware - #114
Merged
Conversation
Every daily refresh since Aug 27 changed only docs_index.npz: OpenAI embeddings are not bit-for-bit reproducible, so re-embedding unchanged text produced a new binary, a commit, a patch release, and a production deploy with no documentation change behind it. Store a content hash per page and per chunk vector, reuse cached vectors for chunks whose hash already exists in the committed artifact, and write the npz with fixed zip timestamps so an unchanged documentation set yields byte-identical files. Only new or edited chunks are sent to OpenAI. The build also reports added, changed, and removed pages (optionally as JSON via DOCS_REPORT_FILE) for the refresh workflow to turn into release notes.
Greptile SummaryThe PR consolidates documentation vectors and metadata into one deterministic, atomically published index artifact.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Important Files Changed
Reviews (6): Last reviewed commit: "test: replace docs index unit tests with..." | Re-trigger Greptile |
…omically Title and description reach search results, so the page hash now covers them and a front-matter edit counts as a change without re-embedding. The vector cache is dropped when the embedding dimension changes, not only the model, so a width change re-embeds instead of failing on assignment. Both artifact files are written to the side and swapped in together so an interrupted build cannot pair a new archive with stale metadata.
…nt builds The two artifact files cannot be swapped in atomically, so stamp both with a fingerprint of the page hashes and have the loader ignore a pair whose stamps differ or whose chunk map does not fit the page list. An interrupted build can no longer be served as a mismatched index.
…p files Include model and dimension in the build fingerprint so vectors from one embedding setup are never served with metadata describing another. Give each build unique temporary file names so overlapping manual builds cannot clobber each other, and keep the swapped-in artifacts world-readable.
Two files can never be swapped in atomically, so fold the page metadata into docs_index.npz as an embedded meta.json member. A single rename publishes vectors and metadata together, an exclusive lock serializes overlapping builds, and the loader no longer has a mismatched pair to guard against. Also embed identical chunk texts once so a fresh build and a cached rebuild produce the same bytes; 273 of 5849 chunks are shared boilerplate whose separate embeddings differed slightly.
Fetch real pages from appwrite.io and run the full build, rebuild, change detection, and server search path with a local embedder standing in for OpenAI. No credentials needed, and the byte-identical and selective re-embedding guarantees are proven on real content rather than fixtures.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Every daily "refresh documentation search index" commit since Aug 27 changed only
docs_index.npz, never the metadata. OpenAI embeddings are not bit-for-bit reproducible, so re-embedding unchanged text produced a different binary, which the cron turned into a commit, a patch release, and a production deploy with no documentation change behind it.What
docs_index.npznow holds the vectors, the chunk to page map, the chunk hashes, and the page metadata as an embeddedmeta.jsonmember.docs_index_meta.jsonis gone. One file is published with one rename, so vectors and metadata can never disagree. An exclusive lock serializes overlapping manual builds.np.savez_compressedstamps entries with the current time, which alone made two identical builds differ.DOCS_REPORT_FILEis set. This is the input for the follow-up that turns the report into release notes.src/mcp_server_appwrite/docs_index.pyowns all of this.docs_search.pyreads metadata from the archive and rejects an artifact whose chunk map does not fit its page list. The daily workflow now stages only the one artifact.What the artifact looks like
src/mcp_server_appwrite/data/docs_index.npzis a plain zip thatnp.loadopens. Every entry carries the same fixed 1980-01-01 timestamp.meta.json(one page shown, content truncated):{ "model": "text-embedding-3-small", "dimension": 1536, "pages": [ { "path": "docs/advanced/billing/abuse", "title": "Abuse policy", "description": "Guidelines on abusive behavior, prohibited activities, and reporting mechanisms under our Fair Use Policy.", "content": "Appwrite is committed to providing a fair, secure, and high-quality experience for all users. This Abuse Policy, as part…", "hash": "b3015ebf07947ec377b44bb25156f132947203d01115df2306bc82630c293b14" } ] }hashis sha256 over title, description, and content, so a front-matter edit counts as a change without re-embedding anything.Build report written to
DOCS_REPORT_FILE(this is a quiet day; on a real change the lists hold{path, title}entries):{ "pages": 586, "chunks": 5849, "chunks_embedded": 0, "chunks_reused": 5849, "changes": { "added": [], "removed": [], "changed": [] } }Console output of the same run:
Verification
docs_index.npz(sha256 compared). The rebuild embedded 0 of 5849 chunks and reported 0 changes.tests/integration/test_docs_index.pyruns end to end against appwrite.io with a local stand-in embedder (no OpenAI credentials): fetches four real pages, builds, rebuilds byte-identically with zero embedding calls, re-embeds only an edited page's chunks, reports metadata-only edits and removed pages, checks the artifact layout above, and round-trips a search throughDocsSearch.Effect on the daily workflow
No further workflow change is needed for the skip:
update-docs-index.ymlalready exits whengit diff --cached --quiet, which now actually holds on quiet days. Release-note summaries from the JSON report are the next PR.