Status: Compatibility-window validation authority
Last Updated: 2026-04-24
Scope: release-grade functional validation, migration rehearsal, migration-state detection, and acceptance gates for the current breaking cleanup generation built on top of the completed compatibility-window upgrade.
Execution record:
docs/development/upgrade-validation-report-20260423.md
docs/development/upgrade-validation-report-20260424.md
docs/development/upgrade-validation-report-20260425-main-v1.3.1.md
2026-04-24 implementation note:
migrate run coverage is citation_styles, toolref, explore, proceedings, spool, papers, and workspacescholaraio migrate status|plan|recover|verify|cleanup|run|finalize|upgrademigrate upgrade --confirm; migrate finalize --confirm remains the lower-level post-store cleanup gateThis document defines what must be tested before the team may claim that the compatibility-window upgrade is correct.
It exists to answer four concrete questions:
This is not another directory-vision document. It is the release gate for the current upgrade generation.
Use the development documents in this order:
docs/development/scholaraio-upgrade-plan.md
docs/development/directory-structure-spec.md
docs/development/directory-migration-sequence.md
docs/development/migration-mechanism-spec.md
docs/development/user-data-migration-strategy.md
docs/development/config-surface-audit.md
docs/development/upgrade-validation-matrix.md
The first six documents define what ScholarAIO should be. This document defines what must be demonstrated before we say it is working.
The upgrade is not complete just because unit tests pass or migrate run succeeds once.
The release may be called correct only when all of the following are true:
A --help screen does not count as functional validation. Every feature family below requires at least one behavioral command that reads, writes, searches, transforms, migrates, or verifies real artifacts.
Validation must use disposable copies, not the operator’s real long-lived runtime root.
Recommended working area:
workspace/release-validation/<stamp>/
├── fixtures/
├── fresh-root/
├── legacy-root/
├── migrated-root/
└── evidence/
Recommended rule:
fresh-root/ proves the new defaults work cleanlylegacy-root/ proves compatibility reading and migration detectionmigrated-root/ proves the post-migration runtime still behaves correctlyevidence/ stores command transcripts, journal snapshots, outputs, and summariesRehearsal roots must be writable.
Reason:
cfg.ensure_dirs()Practical rule:
Each rehearsal root must contain a valid config.yaml.
Commands must be executed in one of two ways:
config.yamlSCHOLARAIO_CONFIG=<rehearsal-root>/config.yamlDo not assume the current shell working directory or a developer’s global ~/.scholaraio/config.yaml will point at the intended rehearsal root.
If the local machine already has a sibling checkout at ../scholaraio/, treat it as the preferred seed source for rehearsal data, but never mutate it in place.
Current useful local sources already observed there:
../scholaraio/config.yaml../scholaraio/config.local.yaml../scholaraio/data/
data/papers/data/explore/data/proceedings/data/inbox*data/pending/data/index.db../scholaraio/workspace/*/papers.json../scholaraio/workspace/annual-review-particle-turbulence/main.pdf../scholaraio/data/inbox/../scholaraio/data/pending/, including paper.md, pending.json, images, and some original PDFsRecommended operator rule:
../scholaraio/ into workspace/release-validation/<stamp>/...../scholaraio/config.local.yaml only when provider-backed canaries are intentionally in scope on the disposable rootconfig.local.yaml is not copied, keep provider-backed surfaces in the release record as explicitly unverified rather than failing deterministic local validationNot all copied fixtures are equivalent.
Use these tiers explicitly:
structural-only
migrate verify if searchable derived state is missing or stalesearchable
keyword_search_accessible, explore_search_accessible, or similar verify checks to passPractical examples from real CLI rehearsal:
data/papers/ without rebuilding index.db may leave keyword_search_accessible failing even when search can still return some resultsexplore library with only papers.jsonl and meta.json is structural-only; a green explore_search_accessible check requires a searchable fixture such as one with explore.db or an equivalent local rebuildproceedings.db is enough for proceedings-volume helper checks, but not for fsearch --scope proceedings ...The validation corpus must cover at least these classes before the release may be called fully validated:
meta.json and paper.mdpapers.jsonexplore libraryproceedings librarypending.jsonIf thesis / patent / document / proceedings-volume ingest is part of the claimed release scope, each class must also have a real sample in the rehearsal corpus. If those samples are missing locally, release signoff must stop until they are copied in or otherwise prepared.
Default release posture:
Durable content that should be copied or migrated intact includes:
data/papers/<paper-dir>/data/pending/<item>/workspace/<name>/meta.json, paper.md, notes.md, translated Markdown, images, layout artifacts, and resume stateDerived state that should normally be rebuilt or re-derived after relocation or downsampling includes:
data/index.db, faiss.index, and faiss_ids.jsondata/topic_model/ or future data/state/topics/data/explore/<name>/explore.db, explore FAISS, and explore topic artifactsdata/proceedings/proceedings.db or future proceedings search DB stateCurrent compatibility-window guidance:
Every validation run should leave a compact but durable evidence bundle under:
workspace/release-validation/<stamp>/evidence/
├── environment.txt
├── commands.txt
├── repo-checks.txt
├── fresh-smoke.txt
├── legacy-smoke.txt
├── online-canaries.txt
├── migration/
│ ├── status-before.json
│ ├── plan.json
│ ├── verify-before.json
│ ├── run-<store>.txt
│ ├── verify-after-<store>.json
│ ├── cleanup-preview.txt
│ ├── cleanup-confirm.txt
│ ├── finalize.txt
│ └── status-after.json
└── summary.md
Minimum evidence expectations:
migrate finalize --confirm output when finalization is part of the rehearsalmigrate status output proving layout_state=normal,
layout_version=1, and the activity-selected latest_journalBefore any store move, classify the root correctly.
| State | Signals | What must still work | Required operator action |
|---|---|---|---|
legacy_implicit |
no instance.json, or compatibility bootstrap sets layout_state=legacy_implicit |
scholaraio migrate status, normal compatibility reads, baseline feature smoke |
run baseline checks, then migrate plan |
normal |
supported instance.json, no active migration.lock |
normal commands, migrate status, migrate verify |
continue normal validation or post-migration regression |
migrating |
migration.lock exists and instance.json.layout_state == migrating |
migration/recovery surface only | stop feature testing; inspect journal; recover or continue migration intentionally |
needs_recovery |
instance.json.layout_state == needs_recovery |
migrate status, migrate recover --clear-lock |
do not continue normal validation until the recovery path is documented and the root is stable again |
| unsupported future layout | instance.json.layout_version newer than running code supports |
migrate status only |
upgrade the program before using the root |
| mixed legacy + fresh content | both old and new store roots exist with divergent content | migrate status, migrate plan, targeted inventory |
stop and record the divergence before claiming a green migration path |
For the current compatibility window, a rehearsal root must inventory these store signals:
| Store | Legacy signal to detect | Fresh target after migration |
|---|---|---|
citation_styles |
data/citation_styles/ |
data/libraries/citation_styles/ |
toolref |
legacy toolref root discovered by Config |
data/libraries/toolref/ |
explore |
data/explore/ |
data/libraries/explore/ |
proceedings |
data/proceedings/ |
data/libraries/proceedings/ |
spool |
data/inbox* and data/pending/ |
data/spool/inbox* and data/spool/pending/ |
papers |
data/papers/ |
data/libraries/papers/ |
Required commands:
scholaraio migrate status
scholaraio migrate plan --migration-id <migration-id>
scholaraio migrate verify --migration-id <migration-id>
The plan must be reviewed before any migrate run is accepted.
Post-migration cleanup rule:
scholaraio migrate upgrade --migration-id <migration-id> --confirmmigrate run --store ... --confirm steps are green, the hardened one-click cleanup path is scholaraio migrate finalize --migration-id <migration-id> --confirmcleanup --confirm; it is the
required operator-facing gate that must:
papers.json to refs/papers.jsonworkspace/_system/layout_state=normal and layout_version=1Before running store moves, classify each supported store in the rehearsal root as one of:
present_searchable
present_structural_only
not_present
blocked
Execution rule:
migrate run --store ... for stores classified present_searchable or present_structural_onlynot_present must be recorded as N/A, not forced through a fake migrationblocked stop release signoff until the block is resolved or explicitly waivedThe release gate is intentionally sequential.
Execution discipline:
status, plan, verify, run, cleanup, cleanup --confirm, finalize --confirm, or upgrade --confirm against the same rootMust pass:
python -m ruff check scholaraio testspython -m ruff format --check scholaraio testsgit diff --checkpython -m mkdocs build --strictpython -m pytest -q -p no:cacheproviderThis gate proves the branch is internally consistent. It does not replace runtime validation.
Goal:
Fresh-root preparation rule:
fresh-root/ only with new-layout stores such as data/libraries/..., workspace/..., and the matching rebuilt or copied derived state needed for the chosen smoke setfresh-root/ with legacy roots such as data/papers/ or data/explore/ if the purpose of that root is to prove fresh-layout defaults../scholaraio/config.yaml is acceptable when it still uses the default compatibility aliases such as data/papers; the current config resolver maps those defaults to fresh-layout locations when only fresh-layout stores are presentMinimum surface coverage:
| Feature family | Representative commands | Minimum acceptance |
|---|---|---|
| startup and config | scholaraio --help, scholaraio setup check |
CLI boots normally and reports usable config state |
| paper retrieval | scholaraio search ..., show ..., search-author ..., fsearch ... |
search returns expected hits and show can load paper content progressively |
| semantic/index path | embed, vsearch, usearch, index |
index DB updates and semantic or hybrid search returns expected records |
| citation graph | refs, citing, shared-refs, top-cited |
graph-derived outputs are readable and non-empty on the seed corpus |
| data quality | audit, repair, rename, backfill-abstract |
commands run against a disposable target and leave expected metadata changes or reports |
| local writing and citation validation | citation-check against a local file or stdin |
real citations are recognized, ambiguous matches are reported, and missing citations are surfaced clearly |
| workspace | ws init, ws add, ws list, ws show, ws search, ws export |
named workspace remains usable and outputs remain under workspace/ |
| export and document outputs | export, document inspect |
at least one real export artifact is created and inspected successfully |
| local diagram rendering | diagram --from-ir ... with a prepared IR fixture |
rendering works and output is written to the expected accessor-backed path; the IR fixture must include title, nodes, edges, and layout_hint, and edges must use from / to keys |
| explore and federated search | explore list/info/search --name <lib> ..., fsearch --scope explore:* ... |
explore stores open correctly and federated search can include them |
| tool documentation | toolref list/search/show/use |
current-version resolution and doc retrieval work against the local toolref store |
| proceedings search | fsearch --scope proceedings ... only when the fixture includes searchable proceedings papers plus proceedings.db |
proceedings search works against a genuinely searchable proceedings fixture |
| proceedings volume helpers | proceedings build-clean-candidates ..., apply-clean ..., or apply-split ... against a proceedings-volume fixture |
proceedings-volume management flows still route even when no searchable proceedings DB exists |
| insights and metrics | insights, metrics --summary |
metrics DB is readable and insights produce stable output |
| citation styles | style list, style show ... |
style store is readable and no path regression is present |
| backup surface | backup list, backup run <target> --dry-run when a target exists |
backup routing still works and a dry-run plan can be produced |
| ingest/import local surfaces | attach-pdf --dry-run, import-endnote --dry-run, import-zotero --local ... --dry-run when a local fixture exists |
import plans and attach plans resolve the right targets without mutating the rehearsal root unexpectedly |
| pipeline local surface | pipeline --list, plus a fixture-appropriate pipeline <preset> --dry-run --inspect or pipeline --steps ... --dry-run --inspect |
pipeline planning works on the chosen fixture type and reports a realistic execution plan |
| topic-model local surface | topics --build and topics --topic ... when BERTopic dependencies are available and the fixture size is appropriate |
topic model build or readback works on the rehearsal corpus |
Important nuance for pipeline:
pipeline --dry-run --inspect by itself is incomplete; current CLI requires either a preset such as full|ingest|enrich|reindex or an explicit --stepspipeline --steps extract,dedup,ingest,embed,index --dry-run --inspectpipeline ingest --dry-run --inspect is the stronger smokeImportant nuance for LLM-backed surfaces:
enrich-toc, enrich-l3, translate, and diagram --from-text are provider-backed canaries, not deterministic local smokesGoal:
Must demonstrate:
data/papers/, data/explore/, data/proceedings/, data/inbox*, and workspace/<name>/papers.json remain readable through compatibility accessorsshow, one workspace command, and one explore / toolref / proceedings read path succeed on the legacy copyRecommended minimum commands:
scholaraio migrate status
scholaraio search <query>
scholaraio show <paper-id>
scholaraio ws list
scholaraio explore list
scholaraio toolref list
scholaraio fsearch --scope proceedings <query>
Goal:
Required outputs:
migrate status on the rehearsal rootplan.jsonverify.jsonMandatory rule:
migrate runStores must be migrated in the currently approved order:
citation_stylestoolrefexploreproceedingsspoolpapersOnly stores classified as present in Section 6.2 should be run. not_present stores are recorded as N/A for that rehearsal root.
Required command pattern:
scholaraio migrate run --store <store> --migration-id <migration-id> --confirm
scholaraio migrate verify --migration-id <migration-id>
Compatibility-window note:
migrate run may record verify.json.status = passed_with_warnings when the only remaining failed checks are explicitly non-authoritative derived-state checks waiting on a documented rebuildmigrate verifyAfter each store move, run the targeted smoke family most affected by that store:
| Store | Required targeted smoke after migrate run |
|---|---|
citation_styles |
style list, style show ... |
toolref |
toolref list, toolref search ..., toolref show ... |
explore |
explore list, explore info ..., explore search ..., fsearch --scope explore:* ... |
proceedings |
fsearch --scope proceedings ... only for a searchable proceedings fixture; otherwise at least one proceedings helper command plus a clear present_structural_only record |
spool |
pipeline --list, then a fixture-appropriate pipeline <preset> --dry-run --inspect or pipeline --steps ... --dry-run --inspect, plus queue-root inventory in verify.json |
papers |
search, show, embed, vsearch, usearch, ws show, export |
Derived-state rebuild expectation after durable store moves:
papers, prefer index --rebuild and embed --rebuild before final migrated-root regressionexplore, rebuild what is currently rebuildable from CLI, such as explore embed --name <lib> --rebuild, then verify copied search stateproceedings, when searchable proceedings behavior is in scope, rebuild or refresh proceedings.db through the available proceedings-volume flows before claiming a green searchable resultGoal:
Must exercise:
scholaraio migrate recover --clear-lockscholaraio migrate cleanup --migration-id <migration-id>scholaraio migrate cleanup --migration-id <migration-id> --confirmAcceptance criteria:
Goal:
Must rerun the high-signal feature families on the migrated root:
setup checkPost-migration acceptance requires all of the following:
instance.json.layout_state == normalmigration.lockOffline validation is not enough for provider-backed surfaces.
The release record must classify each provider-backed surface as one of:
Recommended canaries:
| Surface | Minimum canary |
|---|---|
arxiv |
real search or fetch against arXiv |
explore fetch |
real OpenAlex pull on a tiny query |
refetch / backfill-abstract |
at least one real metadata lookup |
websearch |
one real search |
webextract / ingest-link |
one real rendered-page extraction |
patent-search / patent-fetch |
one real USPTO search and one fetch when a reachable sample exists |
translate |
one real LLM-backed translation |
enrich-toc / enrich-l3 |
one real LLM-backed enrichment |
diagram --from-text or paper-driven IR extraction |
one real LLM-backed diagram generation run |
backup run |
one real dry-run against a configured target; one real transfer if backup behavior is part of the release promise |
| MinerU cloud or local daemon paths | at least one real parse when PDF ingest behavior is claimed |
For translate, a canary is only fully green when all of the following are true:
paper_{lang}.md is written successfully--portable also writes the portable bundle when requested.translate_<lang>/ workdir is removed after successful completionAll provider-backed surfaces must be marked explicitly as unverified rather than silently assumed green when credentials, daemons, or network are unavailable.
The upgrade is not fully validated if only read paths are tested.
At minimum, the validation record should exercise:
attach-pdfpipeline <preset> --dry-run --inspect or pipeline --steps ... --dry-run --inspect run on a prepared inboxIf the release claims thesis, patent, document, or proceedings-volume ingest remains correct, each class must be exercised with a real sample from the rehearsal corpus.
Because this upgrade changed workspace path authority and output accessors, the validation record should always include:
../scholaraio/workspace/translation_bundle_root when translation export is in scopeEvery top-level CLI surface must be explicitly classified in the release record, even if it is waived.
| Surface | Validation class | Minimum expectation |
|---|---|---|
index, search, search-author, show, embed, vsearch, usearch, refs, citing, shared-refs, top-cited |
deterministic local | must be covered in fresh-root and migrated-root regression |
topics |
deterministic local with dependency caveat | run when BERTopic stack is available and corpus size is appropriate; otherwise mark blocked |
backfill-abstract, refetch |
provider-backed or hybrid | run real canaries when release scope claims metadata fetch correctness |
rename, audit, repair, citation-check |
deterministic local | must be exercised against disposable content or local fixture files |
pipeline |
fixture-dependent local | always run pipeline --list; then run a valid preset or explicit --steps dry-run matched to the fixture type |
export, document |
deterministic local | create and inspect at least one real output artifact |
ws |
deterministic local | cover list/show/search/export plus one fresh workspace operation |
import-endnote, attach-pdf |
deterministic local or dry-run | exercise at least one local-file fixture path |
import-zotero |
local or provider/integration | use --local ... --dry-run when a SQLite fixture exists; otherwise classify under canary/integration |
migrate |
deterministic local | status, plan, verify, relevant run, cleanup, and recovery must be exercised on rehearsal roots |
setup, backup, metrics, insights, style |
deterministic local | must be exercised when their configured prerequisites exist |
explore list/info/search |
deterministic local if searchable fixture exists | run against a searchable explore fixture; if only structural fixture exists, mark search blocked |
explore fetch |
provider-backed canary | run only with network/API availability |
proceedings |
local helper or searchable local | distinguish helper-path validation from searchable proceedings validation |
toolref list/show/search/use |
deterministic local | must resolve current version and return real docs from a local toolref store |
toolref fetch |
provider-backed or repo/integration canary | run when tool-doc fetching behavior is part of the claimed release surface and the required source/network access is available |
arxiv, websearch, webextract, ingest-link, patent-search, patent-fetch |
provider-backed canary | run when network and required services are available |
translate, enrich-toc, enrich-l3 |
provider-backed canary | run when LLM backend is available |
diagram --from-ir |
deterministic local | should render from a prepared IR fixture |
diagram --from-text or paper-driven generation |
provider-backed canary | run when LLM backend is available |
Because compatibility readers remain deliberately active in this generation, validation must prove both sides:
Local-clone validation may use python -m scholaraio.cli ... instead of scholaraio ... if the editable install is not active.
Example rehearsal skeleton:
# 1. build disposable rehearsal roots
cp ../scholaraio/config.yaml workspace/release-validation/<stamp>/legacy-root/config.yaml
# optional only when provider-backed canaries are intentionally in scope
# cp ../scholaraio/config.local.yaml workspace/release-validation/<stamp>/legacy-root/config.local.yaml
cp -R ../scholaraio/data workspace/release-validation/<stamp>/legacy-root/data
cp -R ../scholaraio/workspace workspace/release-validation/<stamp>/legacy-root/workspace
# 1b. seed a fresh-layout root for Gate G1
cp ../scholaraio/config.yaml workspace/release-validation/<stamp>/fresh-root/config.yaml
mkdir -p workspace/release-validation/<stamp>/fresh-root/data/libraries
cp -R workspace/release-validation/<stamp>/migrated-root/data/libraries/papers \
workspace/release-validation/<stamp>/fresh-root/data/libraries/papers
cp -R workspace/release-validation/<stamp>/migrated-root/data/libraries/explore \
workspace/release-validation/<stamp>/fresh-root/data/libraries/explore
cp -R workspace/release-validation/<stamp>/migrated-root/data/libraries/toolref \
workspace/release-validation/<stamp>/fresh-root/data/libraries/toolref
cp workspace/release-validation/<stamp>/migrated-root/data/index.db \
workspace/release-validation/<stamp>/fresh-root/data/index.db
# 2. baseline
export SCHOLARAIO_CONFIG=/path/to/legacy-root/config.yaml
python -m scholaraio.cli migrate status
python -m scholaraio.cli migrate plan --migration-id mig-<stamp>
python -m scholaraio.cli migrate verify --migration-id mig-<stamp>
# 3. migrate store by store
python -m scholaraio.cli migrate upgrade --migration-id mig-<stamp> --confirm
# 3b. lower-level equivalent for debugging store-by-store behavior
python -m scholaraio.cli migrate run --store citation_styles --migration-id mig-<stamp> --confirm
python -m scholaraio.cli migrate verify --migration-id mig-<stamp>
python -m scholaraio.cli migrate run --store toolref --migration-id mig-<stamp> --confirm
python -m scholaraio.cli migrate verify --migration-id mig-<stamp>
python -m scholaraio.cli migrate run --store explore --migration-id mig-<stamp> --confirm
python -m scholaraio.cli migrate verify --migration-id mig-<stamp>
python -m scholaraio.cli migrate run --store proceedings --migration-id mig-<stamp> --confirm
python -m scholaraio.cli migrate verify --migration-id mig-<stamp>
python -m scholaraio.cli migrate run --store spool --migration-id mig-<stamp> --confirm
python -m scholaraio.cli migrate verify --migration-id mig-<stamp>
python -m scholaraio.cli migrate run --store papers --migration-id mig-<stamp> --confirm
python -m scholaraio.cli index --rebuild
python -m scholaraio.cli embed --rebuild
python -m scholaraio.cli migrate verify --migration-id mig-<stamp>
# 4. cleanup rehearsal
python -m scholaraio.cli migrate cleanup --migration-id mig-<stamp>
python -m scholaraio.cli migrate cleanup --migration-id mig-<stamp> --confirm
This is only the migration skeleton. The feature smokes and evidence capture listed above are still required around it.
Stop the release process immediately if any of the following occurs:
migrate verify fails after any store moveThe current breaking cleanup generation is release-ready only when:
Until then, “the refactor is mostly done” is not an acceptable release claim.