Owilix Changes #
Human-curated changelog tracking significant changes across branches.
Format #
Each entry follows:
## [Branch] @ [commit-hash] - YYYY-MM-DD
### Summary
Brief description of what was accomplished.
### Changes
- List of specific changes
### Breaking Changes (if any)
- List of breaking changes
Unreleased #
main @ v5.10.9 - 2026-08-08 #
The descriptive record, and two accessors that lied about it #
Summary: Asks 1, 2 and 3a/3b from the openwebindex.eu dashboard. Each was reproduced against live data before being changed.
Changes:
?include=metadataon/remote/lsand/local/ls, sourced fromas_json_dict(). Verified against the realGerman Commonsrecord: 8 creators with ORCIDs whereget()saidLukas Gienapp+7, a 1,385-character description whereget()truncated, and a licence whereget()returnedNone. Sweptas_json_dict()across all 1,864 LEXIS records: zero failures, across all six shape variants.DatasetMetadata.__getitem__now agrees with.get()for advertised-but-absent keys, sodict(md)works. Six keys raised before.RightsListMetadata.short_repr()no longer assumesrightsIdentifier. This is whyrightsListwas missing from the flat projection: theKeyErrorwas swallowed by.get().- Response models declared for the listing endpoints, with coercing validators so typing cannot turn one odd sidecar into a 500.
require_authrenamed torequire_upstream_session, alias kept, wire error code kept, docs made explicit about the absence of caller authentication.
Two mistakes caught during the work, both worth recording:
- Declaring the response model initially emitted
metadata: nullandmetadata_error: nullon every row — precisely the null-filling the report asked us to avoid. Fixed withresponse_model_exclude_unset=True, verified by diffing the default response. - Two workflow tests written yesterday pinned a fixture date and queried a relative
days=7window. They passed on the day written and failed the next morning, looking like a regression in unrelated code. Fixtures are now relative to now.
Breaking Changes: None. include is opt-in, the default listing is byte-identical, require_auth still resolves, and the 401 code is unchanged.
Not addressed (reported, deferred by the reporter): /health cannot fail and there is no readiness probe; /remote/ls pagination re-walks the backend per page with no cache.
main @ v5.10.8 - 2026-08-07 #
The last two plan items: --since, and reading verification back #
Summary: verify --since and GET /remote/collections/{c}/verify. All four gaps recorded in Part III are now closed, with one deliberate non-delivery documented.
Changes:
--sinceskips datasets verified more recently than the cutoff, making a weekly pass over a large mirror practical. Tier order is respected — a recordedchecksumsatisfies areadablerequest, not the reverse — and a record whosecomputed_atis missing or unparseable is treated as stale, so incomplete information re-verifies rather than skips.GET /remote/collections/{c}/verifyexposes the last outcome per dataset withok/failing/unverifiedcounted apart. The distinction is the point: a dashboard that reads "no record" as "failing" reports unverified data as broken.GET /collections/{c}/indexdeliberately not built — the index is an internal optimisation over the sidecars, and the catalog summary already serves consumers. Recorded rather than left implied.
Verified on the fixture: --since 2026-08-06 skipped all 3 as fresh (0 verified); --since 2026-08-09 re-verified all 3; a malformed date is rejected with a validation error rather than silently ignored.
Breaking Changes: None — new flag and new endpoint.
Plan status: M0–M6 complete, all Part III gaps closed.
main @ v5.10.7 - 2026-08-07 #
ls --observed: the claim and the measurement, side by side #
Summary: M1's acceptance criterion, the last operationally meaningful gap in the summarize/integrity plan.
Changes:
observed_totals()incore/tasks/remote.pyaggregatesstats.jsonacross a listing, returningmeasured,unmeasured,observed,declaredand the ids of datasets holding no data — counts rather than one total, because summing across unmeasured datasets produces a number that looks like a measurement and is not.owi remote ls --observedprints it beneath the normal table.
Two design choices worth recording:
- Additional block, not a changed summary line. Mutating the default alters numbers every consumer reads, and the value here is the comparison: declared 570 files against measured 3 says something neither figure says alone.
- Unmeasured is its own count.
0 files measuredand3 datasets never summarizedare different statements, and collapsing them is precisely the 586-vs-4 error this closes.
Verified on the fixture both before and after summarize: unmeasured 3 / measured 0 first, then measured 3 files against declared 570, with the empty dataset named.
Breaking Changes: None — opt-in flag, default output unchanged.
Remaining from the plan: verify --since (F7's last cost control) and a per-collection verification endpoint (M5 shipped in a different shape). Neither is operationally urgent.
main @ v5.10.6 - 2026-08-07 #
Write-back, and the phantom dataset it would have created #
Summary: F5 delivered, plus documentation housekeeping recording which acceptance criteria are still unmet.
Changes:
summarize --write-back/--no-write-back, and--summary --write-backplacing_collection_summary.jsonbeside each collection.- The guard that had to come first:
list()treats any*.jsonin a collection directory as a dataset sidecar, so the aggregate would have appeared in every listing as a phantom — thelatest.jsonfailure shape, self-inflicted.COLLECTION_ARTIFACTSexcludes owilix's own collection-level artifacts from listings, the index, and the fingerprint. Verified on the fixture: 3 datasets before writing the aggregate, 3 after. - Part III of the plan now records shipped-vs-open per acceptance criterion, after "essentially complete" was claimed here once and was wrong.
Tested against the local file:// fixture only. --write-back writes into the repository, and the LEXIS credential in this environment is currently valid, so running it against a real specifier would have written to production storage.
Breaking Changes: None. --write-back defaults to true, which is the previous behaviour.
Still open: remote ls --observed (the number a catalogue reads is still declared-only), verify --since, and a per-collection verification endpoint.
main @ v5.10.5 - 2026-08-07 #
Derived artifacts over HTTP, and checksums that can actually be re-run #
Summary: M5 plus the incremental mode M4 unblocked. Two bugs found by running the code rather than reading it.
Changes:
GET /remote/catalog/summaryandGET /remote/datasets/{id}/summaryexpose what M0–M4 write beside the data. Unmeasured is kept distinct from measured-as-empty, andverification: nulldistinct from verified-and-clean — the same 0-vs-NULL rule, now at the API boundary.- Incremental checksums: reuse a recorded digest when size and mtime both match, labelled
cachedand counted.--recheck-alland--only-missingbound a run. - Bug found while testing:
checksums.jsonwas verifying itself. Self-referential by construction — hashing it changes it — so it could never converge and would rewrite on every run. Derived artifacts are now excluded, which also stops a regenerated README changingdataset_digest. - Bug found while testing:
fill_file_detailsfetched onlysize, so the incremental reuse and the iRODS-checksum preference — both written, both tested against injected data — could never fire against a real repository. Switched tofs.info(), same round trip, and the tests that passed for the wrong reason now exercise the real path.
Also corrected: the new HTTP tests initially passed a NameError off as "no stats", because _read_stats_json swallows exceptions and json was not imported in that test module. Worth noting as a pattern — a helper that catches broadly turns a test bug into a plausible-looking result.
Breaking Changes: None. New endpoints; verification behaviour is additive and --recheck-all restores the previous full-pass semantics.
Still open from the plan: nothing in M0–M6 except cost controls beyond --max-files/--max-datasets/--only-missing (F7's --since), and the CLI/HTTP surfaces for reindex scheduling.
main @ v5.10.4 - 2026-08-07 #
A collection index that refuses to be trusted when it should not be #
Summary: M4. _index.parquet per collection plus remote reindex, shipped together because an index without a rebuild path is a liability rather than a cache.
Changes:
- New
owilix/core/index.py: fingerprinting, build, read, and anIndexUnusableexception whose only correct handling is to walk — raising rather than returningNonemakes that impossible to forget at the call site. FileBasedRepository.list()consults the index per collection and falls through to the existing walk on any failure.remote reindexwalks the sidecars once and writes the index;owi remote lsneeds no flag.- Staleness is a fingerprint over each sidecar's name, size and mtime, recomputed from one
ls. Chosen over a bare object count because it also catches a resized or re-uploaded sidecar at identical cost, since the listing already returns the details.
Verified end to end on the fixture: walk and index return identical results; adding a dataset without reindexing yields 4 not 3; a corrupted index yields 4; a deleted index yields 4. All four paths agree with the source.
Breaking Changes: None. No index means the previous behaviour exactly.
Second customer: M6 tier 3 currently re-reads every byte each pass because it has no way to know what changed. The index is what an incremental checksum mode would build on — that is now the obvious next step for it.
Still open from the plan: M5 (HTTP exposure of the derived artifacts), and incremental checksums on top of this index.
main @ v5.10.3 - 2026-08-07 #
Verification: presence, readability, checksums — and what a hash cannot tell you #
Summary: M6 tiers 2–3. owi remote verify with three cost tiers, format-aware corruption detection, and results stored beside the data.
Changes:
- New
owilix/core/tasks/verify.pyandowi remote verify. Tierspresence/readable/checksum. - Format validation belongs in the checksum tier, per the user requirement: a truncated Parquet hashes perfectly, so hashing alone certifies corruption rather than detecting it.
checksumandformat_validare independent facts;format_valid: nullfor uncheckable formats keeps "unknown" distinct from "invalid" — the same 0-vs-NULL rule as everywhere else in this plan. - Hashes stored alongside the file (
checksums.json) so non-iRODS clients can verify independently;md5kept besidesha256for MD5-only tooling. - iRODS checksums read where available, labelled
checksum_sourceso a reader can distinguish verified from asserted. - Multipart ETag
-Ndetected and refused as MD5; a genuine single-part disagreement is recorded, never repaired. dataset_digestover sorted(path, size, sha256), with unhashed files contributing?so partial can never masquerade as complete.
Verified: on the local fixture, a deliberately truncated parquet produced a valid SHA-256 and format_valid: false with the parser's reason ("Parquet magic bytes not found in footer"), while the healthy dataset reported rows, row groups and columns. checksums.json written for all three datasets.
Breaking Changes: None — new command and a new derived artifact.
Still open from the plan: M4 (_index.parquet + remote reindex) and M5 (HTTP exposure). M6 tier 3 has no incremental mode yet — it re-reads everything each pass, which is what M4's index would fix.
main @ v5.10.2 - 2026-08-07 #
README gets a machine-readable head, without restoring the churn #
Summary: M3 of the summarize/integrity plan. YAML front matter on README.md, prose untouched.
Changes:
_readme_front_matteremits the index-row fields as YAML;split_front_matterparses them back and is tolerant of a malformed or absent head.- The interaction that mattered:
computed_atnow appears in the README too, so the M2 unchanged-write check would never have matched and every README would have been rewritten on every run — reinstating the exact churn that could invalidate a dircache ETag and, before v5.9.0, empty a listing._stats_computed_at_only_diffnow strips volatile keys from front matter as well as fromstats.json, and compares the prose literally. Caught by running the fixture, not by reading the diff. _yaml_safecoerces unrepresentable metadata values rather than raising.
Verified: front matter round-trips; a second --force pass writes nothing; the human section is byte-identical when only the timestamp moves.
Breaking Changes: None. Additive head; existing readers of the prose are unaffected.
Still open from the plan: M4 (_index.parquet + remote reindex — where the risk concentrates, and its own release), M5 (HTTP exposure), M6 (verify tiers, checksums, cost controls).
main @ v5.10.1 - 2026-08-07 #
Observed vs declared, provenance, and an end to write churn #
Summary: M0–M2 of the summarize/integrity plan, in the order the plan set. All additive.
Changes:
- M0 provenance:
stats.jsonrecordscomputed_atandowilix_version. It had none — the fields the plan credited it with areremote doctoroutput from a different command. Provenance was pulled ahead of the index because index rows that cannot say when they were computed reintroduce the ambiguity below at a larger scale. - M0 honest empties:
parquet_files_foundplusmaterialisedseparate measured, empty from nothing measured._generate_stats_jsonreturned the same literal for both. - M1 observed vs declared:
declaredfrom the sidecar,observedfrom the store.observedreports0rather thannullwhen a listing succeeded and found nothing, because zero is a measurement;declaredreportsnullfor fields the sidecar lacks. - M2 content-addressed writes:
_write_if_changedskips a write when bytes would not change.stats.jsonis compared ignoringcomputed_atalone — otherwise the timestamp would defeat the fix — and an unreadable existing object is written rather than assumed unchanged.
Verified on the same three-dataset fixture used to schedule the plan: the dataset declaring 567 files and 250k objects over an empty directory now records observed: {files: 0, bytes: 0}, materialised: false; a second --force pass writes nothing where it previously rewrote every object.
Breaking Changes: None. statistics[] keeps its shape and every new key is additive.
Still open from the plan: M3 (README front matter), M4 (_index.parquet + remote reindex, where the risk concentrates), M5 (HTTP exposure), M6 (verify tiers, checksums, cost controls).
main @ v5.10.0 - 2026-08-07 #
Workflow monitoring, and the reason it had never worked #
Summary: owi workflow (dags, runs, executions, summary) plus four HTTP endpoints, filterable by date and metadata, with JSON everywhere. The blocker turned out to be a one-line auth bug, not missing code.
Changes:
- Root cause first:
AirflowMonitorsentX-Api-Token, which the LEXIS gateway answers with 401 for a valid token. Probed all three variants against the live instance —Authorization: Bearerreturns 200,X-Api-Tokenreturns 401 — so the 963-line module incompat/had never been able to work. That explains why it was unregistered and untested rather than merely neglected. - New shared layer
owilix/core/tasks/workflow.py: window resolution, filtering, summarisation and the error taxonomy, called by both the CLI and the HTTP API so the filters cannot drift. The Airflow client itself is reused fromcompat/workflows.pyrather than rewritten. - CLI
owi workflowregistered in the modern Typer app:dags,runs,executions,summary, each with--json. - Filters:
--dag,--task,--operator,--state,--pool,--queue,--hostname,--min-duration,--since/--until,--days/--hours,--limit/--offset. OR within a field, AND across fields. Field names taken from real task instances (dag_id,task_id,operator,state,duration,pool,queue,hostname, …), not guessed. - HTTP:
GET /workflow/{dags,runs,executions,summary}with the same filters as query parameters, synchronous because they answer in seconds.400bad date /401no credential /502Airflow unreachable; empty stays200withtotal: 0. summarize_executionsis reimplemented in the shared layer rather than reusingAirflowMonitor.summarize, because the latter re-fetches and cannot see a filter — a summary must describe exactly the set that was listed.- Durations report
Nonerather than0when unmeasured, following the rule from the summarize/integrity plan.
Verified live: against the LEXIS Airflow with a fresh token — 9 DAGs, 435 executions in 7 days, 6 failed. The failures are heappe_keep and ref_token_keeper pairs recurring on 2026-08-03, 08-05 and 08-07, i.e. a token-keeper failing roughly every other day. Both CLI and HTTP paths exercised, including date-range filtering and the 400 on a malformed date.
Breaking Changes: None. New command group and new endpoints; nothing existing changed.
main @ v5.9.1 - 2026-08-07 #
Verified the summarize/integrity plan and gave it an execution schedule #
Summary: Docs only. Part II appended to docs/review/summarize_and_integrity_feature_plan_2026-08-07.md: verification against the code, three corrections, milestones M0–M6 with acceptance criteria, and standing rules. Part I left as submitted.
Changes:
- Built a three-dataset collection against a
FileBasedRepository(the class backing s3a remotes) including the 586-vs-4 shape — a sidecar declaring 567 files over an empty directory — and ran the real commands rather than reasoning about them. - Correction 1:
stats.jsoncarries no provenance. The plan credited it withowilix.version/py4lexis.version; those areremote doctoroutput. Provenance becomes a prerequisite (M0), not a preserved property. - Correction 2: the 0-vs-NULL rule the plan asks us to adopt is already violated —
_generate_stats_jsonreturns the same{"values": {}}for "measured, empty" and for "no parquet found". The rule now has a concrete first target. - Correction 3: host stats select a
urlcolumn and silently skip when it is absent. - Measurements kept:
lsreports 570 files / 250,200 objects where 3 / 200 exist;summarizefully describes a dataset holding nothing;--forcerewrites byte-identical objects, confirming F4.1 is churn removal rather than an optimisation. - Sequencing: M0–M3 are small and safe (patch-sized); M4 (index + reindex) is flagged as where risk concentrates, because it puts a derived artifact in the read path — the one place this codebase has repeatedly turned a failure into a confident wrong answer — so it gets fallback-and-warn, a staleness check and its own release.
Breaking Changes: None — documentation only.
main @ v5.9.0 - 2026-08-07 #
One rewritten file no longer empties a mirror listing, successfully #
Summary: M1 from the backlog — the live production bug. GET /remote/ls?specifier=owi-up returned {"total":0,"has_more":false} with HTTP 200 against a bucket holding 591 datasets, while remote doctor called the same repository healthy. All five asks from docs/review/owilix_improvement_brief_2026-08-07.md, in the order given.
Changes:
- Carry the outcome (
owilix/core/listing.py):DatasetListingis alistsubclass withcompleteandincompleteentries of{source, reason, message};/remote/lsand/local/lsexposepartial,failedandincomplete.reasonis a closed set because the reason is what a consumer branches on. This also delivers the listing half of M2 — field naming follows the bug report'spartial/failedwith the M2 proposal'sincompletearray alongside, so both documents' consumers are served. - Per-file tolerance:
list()collects read errors, returns the survivors and reports the skipped count. On its own this turns 591 → 0 into 591 → 590 plus a warning. - Explicit
sizetofs.open: s3fs setsIfMatchonly whensize is None, from adircachethat never expires, so a long-lived server 412s forever on any object rewritten after it was first listed.list()already has the size from the listing it just did. Falls back for filesystems that rejectsize=. status()reads one sidecar and reportsreadable, failing a repository whose metadata cannot be read. An empty repository stays healthy —readableisNone, notFalse, when there is nothing to read.- Prune before loading: an exact
collectionName=filter restricts the directories walked. Regex and negation forms still walk everything, and a pruned walk that finds nothing re-walks —collectionNameis metadata, the directory is convention, and a confidently empty answer is the bug being fixed.
Verification: the reproduction was shrunk and run against both tags. At v5.8.0 it raises OSError [Errno 22] Precondition Failed, which the aggregate turns into an empty listing; at HEAD the same input returns the 5 survivors with complete=False. Suite 621 → 647.
Breaking Changes: None. No existing field or signature changed; DatasetListing is a list, partial defaults to false. Minor rather than patch because the response gained fields and doctor can now report unhealthy where it previously said connected — deployments that alert on doctor output may see a repository flip that was already broken.
Left open: M2's remote pull half (fail on partial discovery, --allow-partial) is a behaviour change for existing callers and was not folded into a fix release. M3 (observed vs declared) untouched.
main @ v5.8.0 - 2026-08-06 #
Auth failures named, listing cost documented, and the small stuff #
Summary: Closes F3, F4, F6 and a new F8 from the mirror-operability review. With v5.7.0 the review is fully released except one item knowingly left open.
Changes:
- F3: New
owilix/core/exceptions.py(OwilixError,AuthenticationError,looks_like_authentication_failure).LexisRepository.listre-raises auth-shaped failures instead of returning[];AggregatedRepository.listre-raises when an auth failure left nothing to return;remote_pullreportsErrorType.AUTH/ExitCode.AUTH_ERROR. Two findings worth keeping: the failure was swallowed at two layers, so fixing either alone would have changed nothing; and detection cannot go by exception type, because py4lexis reports a missing credential asPy4LexisAPIExceptionrather than its ownPy4LexisAuthException— the DDI API answers with anerrorStringit does not recognise. Phrase matching is kept specific, with a test that "Failed to tokenize response body" does not read as a login problem. - F4: Documented on
/remote/lsand inremote ls --helpwith both measurements. The cheap existence check turned out to already exist —FileBasedRepository.statusdoes onefs.existsper access level and never enumerates, and F7 in v5.7.0 is what stopped it raising for those backends — so the docs point atPOST /remote/doctor/statusrather than adding an endpoint. A cheap count is declined with the measurement as the reason: the 84 s call returned zero datasets, soload_metadatawas never reached and all 84 s waslistdir/ls; a shallow count would cost the same. - F6:
ask_yes_nodetects a non-interactive stdin and declines rather than raising fromtermios.tcgetattr. Declining rather than accepting is deliberate —default='y'is what Enter means for a human, not consent from a caller that was never asked. The two bareexcept:incore/tasks/remote.pyare nowexcept Exceptionwith a debug log, so Ctrl-C is no longer swallowed. Needed a module logger in that file; it had none. - F8 (new, found while editing the docstring for F4):
owi remote ls --helpcrashed withMarkupError, because the specifier syntax[/<filter>]in the docstring parses as a Rich closing tag and Typer renders docstrings as markup. Reproduced at v5.7.0, so it long predates this work. Nothing had ever invoked--helpin a test; a sweep over all 57 commands now exists and fails at v5.7.0 on exactlyremote ls.
Breaking Changes: None to the HTTP API shape. remote pull gains exit code 3 for an unusable credential, where it previously reported the v5.7.0 data error (4) or, before that, success. A non-interactive caller that omitted --yes previously crashed and now receives a declined answer — a behaviour change only in the sense that the previous behaviour was a traceback.
Left open, deliberately: a caller still cannot tell that a listing was partial when one repository failed and another answered. Fixing it means changing the list() contract and with it the /remote/ls response shape, so it is recorded under F3 rather than folded in.
main @ v5.7.0 - 2026-08-06 #
Discovery failures are visible, and doctor works on non-LEXIS backends #
Summary: Released F2 and F7 from the mirror-operability review, both found by operating 5.6.0 against a live ~51 TiB mirror rather than by reading the code. Minor rather than patch: both change what an existing call reports.
Changes:
- F2 (
1a11291):remote_pullreturnssuccess=False/ErrorType.DATA/ExitCode.DATA_ERRORwhen discovery matched no dataset, naming the specifier. Deliberately narrow — datasets discovered whose files are already present stays a success and stays exit 0, because conflating the two would make an idle nightly mirror look broken. The HTTP job layer mapssuccess=Falseto afailedjob (http_api/jobs.py:121), so API consumers inherit the distinction without an API shape change. - F7 (
dbf2cca):progress_cb=Noneaccepted and ignored onAbstractRepository.statusandFileBasedRepository.status, forwarded byAggregatedRepository.status.remote doctorcalls it unconditionally atcore/tasks/remote.py:504; onlyLexisRepositoryhad it, so every file-based backend answered with aTypeErrorinstead of a status. - Reviewed both before release. Audited all four
status()implementations and both call sites (cli/admin.py:56passes nothing and is fine on the default); confirmedddi.pyandirods.pydefine no repositorystatus(); confirmed the F2 job mapping actually exists rather than taking the commit message's word for it. - Added the regression test F7 shipped without (
TestStatusProgressCbContract). Written against dynamically discovered backends rather than the three that existed, with a guard against vacuous passes, because F7 was a contract satisfied by one implementation and unenforced everywhere else. Verified to fail atdbf2cca~1with the production error. - Fixed test pollution introduced by F2's own counter-case test: it requested
tmp_pathand never used it, so_write_pull_sidecars→core/sync.pyresolved a bareMagicMockthrough__fspath__and mkdir'dMagicMock/mock.local.create().path/<id>/into the repo root on every run. Fixed rather than gitignored, and the absence is now asserted.
Breaking Changes: None to the HTTP API shape. remote pull's exit code and success flag change for the zero-discovered case — intended, and the reason for a minor bump. Automation that alerts on non-zero exit will now surface a permanently mis-scoped specifier instead of passing silently.
main @ v5.6.0 - 2026-08-04 #
Remote pull --push-to source selection, and HTTP logout / doctor status #
Summary: Fixed a self-cancelling query that made remote pull --push-to report "0 datasets found" and exit 0 for nearly every specifier — the reason the OWI mirror could not be filled. Landed the two HTTP endpoints the accompanying operability review called for, and recorded the review itself.
Changes:
remote_pullnow passesspec.get("data_center")as the listing source instead of falling back topush_to_remote. The fallback combined withignore_data_centers=[push_to_remote]into "list from X while excluding X", which is empty by construction. It hit every specifier whose data center isNone—all, and everyit4i:/lrz:zone alias, since the aliases reset the data center before setting a zone query — so only direct repository specifiers (lexis:…) ever worked.remote_lswas never affected because it already passed the data center alone, which is whylsandpulldisagreed on identical input.ignore_data_centersis deliberately kept.remote_doctorreturns itsresultsdict asCommandResult.objectwhenas_jsonis set (previously alwaysNone), so the per-repository status, backend, DDI counts and per-zone breakdown are reachable without scraping rendered console output.- Added
POST /auth/logout(DeviceAuthManager.logout()/clear_device_states()): deletes the refresh token, drops pending device logins, and returns409 env_token_auth_activewhenPY4LEXIS_TOKENis set, since the server cannot revoke a credential handed to it by its environment._poll_until_completenow returns rather than raisingKeyErrorwhen its state was cleared underneath it — reachable once states can be cleared. - Added
POST /remote/doctor/statusfor a synchronous status document;POST /remote/doctorstays the async job form. - Recorded
docs/review/remote_pull_and_mirror_operability_2026-08-04.md: findings reproduced against the live clusters rather than inferred. F1 (the--push-todefect) and F5 (doctor unreadable over HTTP) ship here. F2 remains open and is the most valuable of the rest — zero discovered datasets still takes the success path and is indistinguishable from "already up to date", which is what hid F1 for weeks. F3 (auth failure surfacing as aTypeErrorduring error formatting), F4 (an 84 sremote lsagainst object-store remotes) and F6 (two bareexcept:,ask_yes_noassuming a TTY) are also open. - Verified during release that the review's F6 note about
eval(files)does not apply to any build carrying the HTTP API: the bare form existed through v5.2.0,ast.literal_evallanded in v5.3.0 (fb24445), and the HTTP API first shipped in v5.5.0. Recorded as an upgrade note inCHANGELOG.mdrather than a fix. - Bumped package metadata to v5.6.0 (minor, not patch: two new endpoints) and refreshed the
Readme.mdversion badge, stale at 5.5.0 since that release.
Breaking Changes: None. Both endpoints are additive and no existing response shape changed.
main @ v5.5.2 - 2026-07-15 #
Query slice CIFF index fix #
Summary: Fixed query slice writing a header-only (empty) index.ciff.gz for the sliced dataset, and restored compatibility with ciff-toolkit 0.2.x.
Changes:
- Filter each source dataset's CIFF index into its own temp file and merge the non-empty results per partition (previously every source was filtered onto the same target path, so the last source — often without matching documents — clobbered the index).
- Warn on the console when a sliced index contains 0 documents or a partition has no matching source index.
- Ported
owilix/core/ciff.pyto the ciff-toolkit 0.2.xCiffWriterAPI and bumped the dependency pin to>=0.2.2;drop_parallelreturns(kept_docs, postings_lists). - Fixed
UnboundLocalErrorwhen--searchis combined with CIFF slicing; normalized parquetidvalues (incl. binary columns) before matching. - Added regression tests (clobbering, merging, partitioned layouts, binary ids, zero matches,
keep=False) and a network-free end-to-end CLI test that asserts sliced index document counts. - Bumped package metadata for the
v5.5.2patch release.
Breaking Changes: None (requires ciff-toolkit>=0.2.2 at install time).
main @ v5.5.1 - 2026-06-02 #
Remote pull push-to payload routing fix #
Summary: Fixed LEXIS-backed remote pull --push-to <repo> so dataset payload files are streamed into the configured destination filesystem instead of being written under the process working directory.
Changes:
- Made
LexisRepository.get(..., filesystem=...)copy from the LEXIS filesystem into the provided destination fsspec filesystem. - Preserved existing local-download behavior when no destination filesystem is supplied.
- Added regression coverage using in-memory fsspec filesystems.
- Bumped package metadata for the
v5.5.1patch release.
Breaking Changes: None.
feature/http-server @ v5.5.0 - 2026-05-30 #
Optional HTTP API preview #
Summary: Added an optional FastAPI-based HTTP API for local OWILIX automation and controlled demos, released as a preview local service with explicit deployment limits.
Changes:
- Added
owilix http servethrough the optionalhttpextra. - Added health, device-login auth, jobs, local, remote, query, config, and plugin endpoints.
- Added in-process background jobs for long-running operations and pollable
/jobs/{job_id}status. - Added optional local S3-compatible facade mounted at
/s3. - Added local dataset quota status, quota configuration, and dry-run-by-default cleanup endpoints.
- Added Docker HTTP compose support with localhost-only published port by default.
- Documented the HTTP API preview, local-only release posture, S3 facade, quota controls, and known limits.
Breaking Changes: None.
feature/remote-sync-sidecar @ 1b1ebafd9ad408abbc4d8784e62f27f74272a5b8 - 2026-04-30 #
Remote sync sidecar workflow #
Summary: Added a sidecar-based remote sync workflow for pull/status/push operations so users can pull a dataset, edit local files, inspect dirty state, and push changed files without requiring remote-side checksums.
Changes:
- Added
.owi-sync.jsonand.owi-files.json.gzsidecars capturing local and remote file baselines fromremote pull - Added
owi remote statuswith local dirty-state detection, optional remote refresh, details output, generated-file inclusion, and explicitnot-foundrows - Made
owi remote pushupload sidecar-detected local additions and modifications while excluding sync sidecars and editor swap files - Refreshed sidecar baselines after successful sidecar-aware push
- Fixed dataset ID handling in
local freeandremote push - Fixed metadata update regressions during remote push
- Documented the implemented workflow under
docs/features/implemented/remote-sync-sidecar-inventory-workflow.md
Breaking Changes: None.
main - 2026-02-25 #
Remote upload S3-source support and release preparation #
Summary: Added direct S3 URI source support to owi remote upload, aligned repository upload plumbing for external filesystems, and prepared release metadata for v5.2.0.
Changes:
- Extended
remote_uploadsource handling to support local paths pluss3:///s3a://URIs - Passed external source filesystems through aggregate/file/lexis repository
put()methods - Added unit coverage for unsupported source schemes and S3-backed upload flows
- Updated remote command documentation and examples
- Updated installer dependency handling for
python-http-irods-clientto satisfyuvdirect URL requirement during install - Pinned installer-side
py4lexis==5.0.1to avoid transitive URL dependency drift conflicts inuv - Bumped package version and finalized release changelog entries for
v5.2.0
multifs - 2026-02-17 #
Multi-filesystem and CLI consistency updates #
Summary: Improved multi-filesystem behavior across CLI and manager flows, with corresponding tests and docs updates.
Changes:
- Extended HTTP-to-iRODS fsspec integration in
owilix/core/fsspec/http2irods.py - Updated CLI and context/output handling in
owilix/cli/modules - Updated manager/query flow integration in
owilix/core/manager/manager.pyandowilix/core/tasks/query.py - Expanded unit coverage in
tests/owilix/core/fsspec/test_core_fsspec_unit.py - Updated dependency lock and configuration docs (
uv.lock,pyproject.toml,docs/source/config.md)
py4lexis4 - 2026-01-02 #
Py4Lexis 5.x Migration Complete #
Summary: Full migration to py4lexis 5.x HTTP APIs, replacing python-irodsclient with HTTP-based iRODS access.
Changes:
- Added
Http2IrodsFileSystemfsspec backend for HTTP-based iRODS access - Created
owilix/core/repository/modular package (replaces monolithic repository.py) - Added
OWILIXSessionwrapper for unified session management - Implemented configuration V3 with migration support (
owi config migrate) - Added new display formats:
--format=short|wide|markdownforowi remote ls/owi local ls - Added config commands:
config version,config migrate,config delete,config reset - Improved test infrastructure with progress indicators
Breaking Changes:
- Deprecated repositories
it4i,lrzremoved - replaced by unifiedlexis - Config auto-migrated from V2 to V3 via
owi config migrate
main - 2026-01-01 #
Documentation Alignment #
- Added
docs/source/contributing/section with symlinks to dev docs - Updated
index.rsttoctree to include contributing section - Added AI agent onboarding section to
docs/epics.md - Fixed external links in dev docs for Sphinx compatibility
- Created branch documentation at
docs/branch/feature-docs-alignment.md
History #
main @ initial - 2026-01-01 #
Summary #
Initial AI infrastructure setup for owilix.
Changes #
- Added
.agent/workflows/with development, testing, documentation, and release workflows - Created
docs/architecture.mdwith project overview - Created
docs/CODING_GUIDELINES.mdwith coding standards - Created
docs/epics.mdfor epic tracking - Created
docs/branch/for per-branch documentation - Created
docs/external/py4lexis.mdwith best practices - Fixed pyproject.toml merge conflict from poetry→uv migration