Guidance for future Codex work in this repository.
This repo builds the eXist-db Algolia index plugin:
- Main package:
org.humanistika.exist.index.algolia - Version source:
version.sbt - Assembly artifact:
target/scala-2.13/exist-algolia-index-assembly-<version>.jar - Important runtime state:
algolia-index/under the eXist data directory - Deployment status file:
algolia-index/status.json
Do not print secrets from .env, eXist conf.xml, SSH commands, Docker env, or Algolia API headers in final answers. If command output includes secrets, summarize without repeating them.
Run these from this repo unless noted.
sbt test
sbt assembly
git diff --check
./scripts/test-indexing-status-verifier.shUseful script syntax checks:
bash -n scripts/exist-common.sh scripts/exist-local.sh scripts/exist-stage.sh scripts/exist-stage-remote.shGitHub Actions workflow: .github/workflows/ci.yml.
CI runs on:
ubuntu-latest,macos-latest,windows-latest- Java
17and21
Each matrix job runs:
sbt --batch -v -Dfile.encoding=UTF-8 assembly
sbt --batch testFor release work, local sbt test and sbt assembly are necessary but not a substitute for CI. After pushing release-prep changes or release commits, check the GitHub Actions result:
gh run list --repo BCDH/exist-algolia-index --limit 5
gh run watch --repo BCDH/exist-algolia-indexThe build uses javacOptions ++= Seq("--release", "8"), while CI validates with modern JDKs. Do not remove Java 8 bytecode compatibility without an explicit compatibility decision.
The main plugin install/test helpers are:
scripts/exist-local.shscripts/exist-stage.shscripts/exist-stage-remote.shscripts/exist-production-hotpatch.sh
scripts/exist-stage.sh is the local SSH wrapper. It uploads the assembly and helper scripts, then runs exist-stage-remote.sh on the staging host.
Common staging commands:
./scripts/exist-stage.sh run
./scripts/exist-stage.sh run --skip-build
./scripts/exist-stage.sh run --skip-reindex
./scripts/exist-stage.sh reindex-collection /db/apps/raskovnik-data/data
./scripts/exist-stage.sh reindex-collection /db/apps/raskovnik-data/data/MBRT.RDG
./scripts/exist-stage.sh verify-collection-sync /db/apps/raskovnik-data/data/MBRT.RDG
./scripts/exist-stage.sh reconcile-collection /db/apps/raskovnik-data/data/MBRT.RDGThe plugin writes deployment-readable state to:
<exist-data-dir>/algolia-index/status.json
States:
current: indexing for that index/collection is healthy.degraded: Algolia write/delete failed; retry/reindex is needed.stale_local_store: the plugin could not derive collection-delete object IDs from the local store.
Local and staging helper verification should fail when status.json contains degraded or stale_local_store. Missing status.json is intentionally treated as OK for first installs and older deployments.
In addition to status.json, the helper scripts now expose explicit live-vs-local verification and recovery commands:
verify-collection-sync /db/apps/raskovnik-data/data/<DICT_ID>inspect-collection-sync /db/apps/raskovnik-data/data/<DICT_ID>replay-collection-live /db/apps/raskovnik-data/data/<DICT_ID>refresh-indexing-status /db/apps/raskovnik-data/data/<DICT_ID>reconcile-collection /db/apps/raskovnik-data/data/<DICT_ID>as an exceptional fallback only
Operator safety contract after the production incident:
- Never mutate stage or production Algolia before read-only diagnosis.
verify-collection-syncandinspect-collection-synccome first.- If live/local sync is clean but
status.jsonis stale or incomplete, userefresh-indexing-status. - If live records drifted under the wrong collection path or live records are missing while the local store is healthy, use
replay-collection-live. - Do not treat
reconcile-collectionas the routine next step. It mutates the local store and is disabled by default for production. - Never copy local-store snapshots from macOS or a workstation without stripping
._*,.DS_Store, and similar sidecar files first.
reconcile-collection still quarantines matching local-store document directories under algolia-index/quarantine/<index>/<timestamp>__<collection-slug>/, reruns xmldb:reindex(...), and re-verifies until sync converges or times out. Those quarantine directories are intentional forensic backups; do not delete them automatically during the command.
Staging uses SSH and a Dockerized eXist container. The relevant env vars live in this repo’s .env, with names like:
EXIST_STAGE_HOSTEXIST_STAGE_SSH_USEREXIST_STAGE_CONTAINER_NAMEEXIST_STAGE_ADMIN_PASSWORDEXIST_STAGE_ALGOLIA_APPLICATION_IDEXIST_STAGE_ALGOLIA_ADMIN_API_KEYEXIST_STAGE_REINDEX_COLLECTION
Do not echo these values.
Known staging caveat from the 1.1.3 pre-release test:
- The container runtime
CLASSPATHpointed at/exist/lib/exist-algolia-index-assembly-1.1.2.jar. - The staging helper copied the new
1.1.3-SNAPSHOTjar bytes both to the new versioned filename and to the runtime classpath path, then verified byte equality. - This means the runtime path name may look old while the file contents are the current build.
Do not use directory existence checks that copy large container directories. The staging image is minimal and may not have a shell or standard tools. For file checks, docker cp container:/path tmpfile is fine. For known directories such as /exist/lib or /exist/data, prefer configured/default paths over probing by copying.
Remote Raskovnik eXist containers are minimal and do not provide sh, bash,
ls, or other shell utilities inside the container. For diagnostics, use
host-level docker logs, docker top, docker stats, docker inspect,
bind-mounted logs under /opt/raskovnik-*-exist/logs, or
docker exec ... java org.exist.start.Main client ... for eXist client checks.
Recent successful staging validation for the 1.1.3-SNAPSHOT candidate on 2026-05-03:
- Installed current assembly into
existdb-stage. - Restarted staging eXist.
- Smoke reindex wrote local store JSON and reached Algolia.
- Smoke Algolia index was deleted afterward.
- Full reindex of
/db/apps/raskovnik-data/datacompleted in about 6 minutes. - Final status verification passed:
OK (3 record(s) checked). rasindex was queryable afterward and reported 84,846 hits.- The full reindex produced zero new
rasadditions; all real-index batches wereadditions=0 updates=0 deletions=0.
From this repo, the current Raskovnik full stack lives under:
../../ttasovac/
Before making edits in any sibling repo from that workspace, read the target
repo's AGENTS.md first and follow its local workflow and build/deploy
contract instead of patching generated output opportunistically.
Main repos:
- Backend/eXist data deploy repo:
../../ttasovac/raskovnik-backend - Laravel frontend repo:
../../ttasovac/raskovnik-frontend
Useful operator shortcut:
- The dotfiles repo has a fish function named
infraat../../ttasovac/dotfiles/fish/functions/infra.fish. Check it when looking for local infrastructure shortcuts or host/container command scaffolding around the Raskovnik stack.
This repo builds and deploys Raskovnik eXist packages and dictionary XML resources.
Important files:
.env: local/staging/production deploy configuration and secrets. Do not print values.scripts/exist-common.sh: shared deploy helpers, manifest reading, status checks, smoke tests.scripts/exist-local.sh: local eXist deployment.scripts/exist-remote.sh: local operator entrypoint for staging/production over SSH.scripts/exist-remote-helper.sh: server-side helper uploaded and invoked byexist-remote.sh.metadata/dictionary-version-manifest.json: dictionary versions and hashes.metadata/component-version-manifest.json: app/data-core versions.dictionaries/<DICT>/src/main/xar-resources/<DICT>.pcified.xml: dictionary XML resources.data-core/src/main/xar-resources/collection.xconf: collection indexing config.
Common backend commands:
./scripts/exist-local.sh run
./scripts/exist-local.sh verify-state
./scripts/exist-local.sh --dict MBRT.RDG run
./scripts/exist-local.sh --all-dicts run
./scripts/exist-remote.sh verify-state --env staging
./scripts/exist-remote.sh run --env staging
./scripts/exist-remote.sh --dict MBRT.RDG run --env staging
./scripts/exist-remote.sh --all-dicts run --env staging
./scripts/exist-remote.sh run --env staging --release-set latestBackend behavior:
- Dictionary deploys update XML resources in existing collections under
/db/apps/raskovnik-data/data/<DICT_ID>. - This avoids package removal and prevents eXist/Algolia from seeing a collection deletion.
data-coreconfig updates updatecollection.xconfand then reindex/db/apps/raskovnik-data/data.- Full
verify-statechecks app/data-core versions, dictionary package presence, dictionary collections, resource hashes, API user/group, installed manifests, and Algolia indexing status. --dict ... verify-statechecks only selected dictionaries.
Important recent backend helper fixes:
- Remote
verify-statemust not requirejqorshasumon the staging host. - It should use
python3for JSON parsing when checking Algolia status. - The
exist-remote.shwrapper must preserve failing remote helper exit codes. - Dictionary hash grep checks must not span multiple XML lines; each dictionary line must match on its own.
Staging state observed during the 2026-05-03 pre-release test:
- Backend
verify-stateruns on staging after those helper fixes. - It correctly fails against the current dirty backend working tree because
GE.RKMDandVSK.SRresource hashes differ from staging. - This mismatch is a backend content-state issue, not an Algolia plugin reindex failure.
This is the Laravel frontend/admin app.
Important files and concepts:
.env: frontend runtime config and deployment-status endpoints/tokens. Do not print values..env.example: documents deployment-status config keys.php artisan restxq:cache-clear: clears only the RESTXQ cache for the active Laravel environment.php artisan deployment-status:write-local: writes local deployment status.php artisan deployment-status:refresh --target=<env>: refreshes cached deployment-status data.- Deployment status endpoint:
/internal/deployment-status. - Admin dashboard includes deployment status cards.
Relevant frontend implementation areas:
app/Console/Commands/WriteLocalDeploymentStatus.phpapp/Http/Controllers/DeploymentStatusController.phpapp/Services/DeploymentStatus/*config/deployment-status.phpresources/views/admin/parts/deployment-status-card.blade.php- tests named
DeploymentStatus*andLocalDeploymentStatusCollectorTest
Backend scripts call frontend hooks:
- Local backend deploys default to sibling
../raskovnik-frontend. - Remote backend deploys use
RASKOVNIK_REMOTE_<ENV>_FRONTEND_DIR. - After backend deploy verification, remote hooks run:
php artisan restxq:cache-clearphp artisan deployment-status:write-localphp artisan deployment-status:refresh --target=<env>
Frontend staging deploy is artifact-based; server deploy normally unpacks a release tarball, links shared state, runs Laravel cache/migrate steps, and switches the current symlink. Do not assume editing frontend files locally changes staging.
The sibling raskovnik-backend and raskovnik-frontend worktrees are often dirty when we work on multiple strands at the same time. Do not reset or checkout files there unless explicitly asked. If a temporary dictionary edit is needed for an indexing test, copy the exact file bytes to a temp backup first and restore them exactly afterward.
Before release work, check:
git status --short --branch
git -C ../../ttasovac/raskovnik-backend status --short --branch
git -C ../../ttasovac/raskovnik-frontend status --short --branchDo not proceed with release tags, release commits, or GitHub Release publication until:
git diff --checkpasses.sbt testpasses.sbt assemblypasses../scripts/test-indexing-status-verifier.shpasses.- Staging smoke install/reindex passes when requested.
status.jsonverification reports nodegradedorstale_local_storerecords.
There are no repo-local prepare-release.sh or prepare-next-snapshot.sh scripts. The maintained release flow for this repo is manual Git tagging plus a GitHub Release with the assembly jar asset.
Do not use sbt release, sbt publishSigned, or Maven repository publishing unless the maintainer explicitly asks for that workflow. The build is intentionally configured for assembly-jar distribution through GitHub Releases, not Maven repository publication.
Version source:
version.sbt
Example format:
ThisBuild / version := "1.1.3-SNAPSHOT"Release bump policy:
- Use patch-version snapshots after a patch release: for a
1.1.3release, prepare1.1.4-SNAPSHOTafterward.
-
Inspect all relevant worktrees and identify which changes are expected:
git status --short --branch git -C ../../ttasovac/raskovnik-backend status --short --branch git -C ../../ttasovac/raskovnik-frontend status --short --branch
-
Confirm
version.sbtis still on the snapshot for the release candidate, for example1.1.3-SNAPSHOT. -
Run local verification:
git diff --check bash -n scripts/exist-common.sh scripts/exist-local.sh scripts/exist-stage.sh scripts/exist-stage-remote.sh ./scripts/test-indexing-status-verifier.sh sbt test sbt assembly -
Run staging validation when the release affects runtime behavior:
./scripts/exist-stage.sh run
If the assembly was already built and only helper scripts changed:
./scripts/exist-stage.sh run --skip-build
-
Confirm staging reindex/status outcome:
- Smoke record reaches Algolia.
- Smoke index is deleted afterward.
- Target reindex completes.
- Final status verification says
OK. - No
degradedorstale_local_storerecord remains instatus.json.
-
If using Raskovnik backend verification as an additional deployment check, run it from the sibling backend repo:
cd ../../ttasovac/raskovnik-backend ./scripts/exist-remote.sh verify-state --env stagingA failure caused by dirty backend dictionary resource hashes is a backend content-state mismatch, not automatically an Algolia plugin failure. Read the XML output before drawing conclusions.
After fixes and validation pass, commit the plugin repo changes before preparing the release version. Keep the commit focused and do not include unrelated sibling-repo changes.
git status --short
git add <intended files>
git commit -m "Prepare Algolia index release"Use a more specific commit message when appropriate. If the user asked to commit, do it; otherwise confirm before creating release commits/tags.
-
Change
version.sbtfromX.Y.Z-SNAPSHOTtoX.Y.Z. -
Run:
git diff --check bash -n scripts/exist-common.sh scripts/exist-local.sh scripts/exist-stage.sh scripts/exist-stage-remote.sh ./scripts/test-indexing-status-verifier.sh sbt test sbt assembly -
Commit and tag the release version:
git add version.sbt git commit -m "Release X.Y.Z" git tag -a "vX.Y.Z" -m "Release X.Y.Z"
-
Push the release commit and tag deliberately:
git push origin master --follow-tags
If the branch is not
master, replacemasterwith the current release branch. Do not create the GitHub Release until thevX.Y.Ztag exists onorigin. -
Create the GitHub Release and upload
target/scala-2.13/exist-algolia-index-assembly-X.Y.Z.jaras the release asset. -
Change
version.sbtto the next snapshot, for exampleX.Y.(Z+1)-SNAPSHOT, and build it if requested. -
Commit the next snapshot:
git add version.sbt git commit -m "Prepare next snapshot"
After release/tag/GitHub Release publication:
git status --short --branch
git tag --points-at HEADConfirm whether HEAD is the release commit or the next-snapshot commit. In the manual flow above, the tag points to the release commit. After the next-snapshot commit, HEAD should no longer be the tagged commit.
Build the next snapshot once if requested:
sbt assemblyThe public GitHub Releases page is the distribution surface for this project. Existing releases at https://github.com/BCDH/exist-algolia-index/releases have:
- Tag/name format:
vX.Y.Z - Uploaded asset:
exist-algolia-index-assembly-X.Y.Z.jar - Human-written release notes
Before creating the GitHub Release, make sure the release tag exists locally and remotely, and make sure the non-snapshot assembly jar exists:
git tag --list "vX.Y.Z"
git ls-remote --tags origin "vX.Y.Z"
test -f target/scala-2.13/exist-algolia-index-assembly-X.Y.Z.jarPrepare release notes in a temporary file. The notes should be concise and operator-facing. Recent release styles:
v1.1.2: short title plus bullet list of operational changes.v1.1.1: generated full changelog only.v1.1.0: fuller notes with Highlights, Notable changes, Upgrade notes, and Asset.
For a normal release with explicit notes:
cat >/tmp/exist-algolia-index-release-X.Y.Z.md <<'EOF'
Release vX.Y.Z
- Summarize the main runtime changes.
- Summarize operator/deploy impact.
- Mention required reindex or status checks if relevant.
EOF
gh release create "vX.Y.Z" \
"target/scala-2.13/exist-algolia-index-assembly-X.Y.Z.jar" \
--repo BCDH/exist-algolia-index \
--title "vX.Y.Z" \
--notes-file /tmp/exist-algolia-index-release-X.Y.Z.mdIf the release should be marked as latest, either let GitHub infer it or pass --latest when using a gh version that supports it. Do not mark a prerelease/snapshot as latest.
Verify the created release:
gh release view "vX.Y.Z" \
--repo BCDH/exist-algolia-index \
--json tagName,name,isLatest,url,assetsConfirm the asset list includes exactly the intended release jar:
exist-algolia-index-assembly-X.Y.Z.jar
If a release was created with the wrong notes or missing asset, prefer gh release edit or gh release upload for small corrections. Delete and recreate a release only when the maintainer explicitly approves it.
Do not hotpatch production with staging variables. Production hotpatching goes through scripts/exist-production-hotpatch.sh with EXIST_PRODUCTION_* variables, and the durable fix is to bake the released plugin into the production image.