Skip to content

breaking: implement the new sysand index read protocol#288

Merged
andrius-puksta-sensmetry merged 122 commits intosensmetry:mainfrom
consideRatio:pr/client-read-new-index
May 6, 2026
Merged

breaking: implement the new sysand index read protocol#288
andrius-puksta-sensmetry merged 122 commits intosensmetry:mainfrom
consideRatio:pr/client-read-new-index

Conversation

@consideRatio
Copy link
Copy Markdown
Collaborator

@consideRatio consideRatio commented Apr 15, 2026


The client now reads remote indexes through the static-file protocol
documented in docs/src/index-protocol.md, with the publish-side API
scope split into docs/src/index-api-protocol.md.

This breaks compatibility with the old HTTP registry layout and with
0.3 registry-shaped lockfiles. Users must regenerate lockfiles, and
index servers must serve the new discovery, index.json, versions.json,
and per-version file layout.

Wire-format changes (reads):

  • <discovery-root>/sysand-index-config.json is fetched lazily on first
    index use to resolve index_root and api_root. A 404 defaults both
    roots to the discovery root; other non-2xx responses are hard errors.
  • index_root / api_root must be absolute HTTP(S) base URLs without
    userinfo. Relative URLs, credentials in URLs, and non-base paths are
    rejected.
  • <index_root>/index.json is now project enumeration. Each projects
    entry carries iri and optional status; omitted status is
    equivalent to available.
  • index.json project status supports available and removed.
    List-all operations filter removed projects, while direct resolution of
    a specific IRI still goes through that project's versions.json.
  • Per-project versions.json replaces the old versions.txt list. Each
    entry carries version, usage, project_digest, kpar_size,
    kpar_digest, and optional status.
  • versions.json entries are validated at ingest: SemVer only, no build
    metadata, descending precedence order, no duplicates, and lowercase
    sha256:<64-hex> digests.
  • versions.json status supports available, yanked, and removed.
    New resolution filters to available versions; direct reads of removed
    versions fail distinctly; yanked versions remain readable for pinned
    lockfiles.
  • Per-version layout becomes <project>/<version>/.project.json,
    .meta.json, and project.kpar.
  • IRI path mapping is pkg:sysand/<publisher>/<name>
    <publisher>/<name>/; all other IRIs use
    _iri/<sha256(normalized_iri)>/.
  • Non-canonical pkg:sysand/... IRIs are hard-rejected through the new
    purl validation path, including
    InterchangeProjectUsageRaw::validate.
  • project_digest is verified before .project.json / .meta.json are
    exposed. kpar_digest and kpar_size are enforced during streamed
    archive download.

Wire-format changes (publish):

  • sysand publish --index now treats the configured URL as the discovery
    root, performs mandatory discovery with the configured
    HTTPAuthentication policy, and posts to <api_root>/v1/upload.
  • Publish still supports direct API-root URLs because a missing discovery
    document defaults api_root to the configured URL.
  • Publish credentials are matched after discovery against the actual
    upload URL. Discovery can therefore be auth-gated while upload remains
    bearer-only.
  • Publish payload preparation now runs before network access, rejects
    SemVer build metadata, enforces the KPAR size limit, and sends
    archive-integrity metadata including the KPAR SHA-256 digest.

Lock/sync format changes:

  • Lockfile format bumps from 0.3 to 0.4; old registry-shaped
    lockfiles are rejected with a regenerate message.
  • Source::Registry is renamed to Source::Index.
  • New Source::IndexKpar records index_kpar, index_kpar_size, and
    index_kpar_digest, preserving the raw-archive integrity tripwire from
    lock to sync.
  • sync can now install index-backed KPARs directly from the lockfile and
    verifies the recorded size/digest without re-reading versions.json.
  • Lockfile validation now distinguishes canonical project digest format
    from index KPAR digest format and rejects uppercase index KPAR digests
    before canonicalization.

Public Rust API:

  • New modules: env::discovery, env::index, project::index_entry,
    and purl. Removed: env::reqwest_http::HTTPEnvironmentAsync.
  • IndexEnvironmentAsync replaces the old HTTP registry environment and
    lazily resolves discovery endpoints.
  • CombinedResolver / CombinedProjectStorage terminology changes from
    Registry to Index.
  • standard_index_resolver and standard_resolver now return Result
    because discovery-root shape validation can fail.
  • ProjectRead / ProjectReadAsync wrappers must explicitly forward
    get_info, get_meta, version, usage, and
    checksum_canonical_hex; the derive macro now forwards these methods.
  • CanonicalizationError::map_project_read is added for wrapper error
    forwarding.
  • ReqwestKparDownloadedProject now accepts optional expected KPAR
    digest/size in new and new_guess_root, verifies them during
    download, and exposes ensure_downloaded_verified /
    is_downloaded_and_verified. Builder setters like
    with_expected_sha256_hex / with_expected_size were removed.
  • ReqwestKparDownloadedError gains DigestMismatch and SizeMismatch.
  • InterchangeProjectValidationError::MalformedSysandPurl is added.
  • core/Cargo.toml adds optional idna under networking and enables
    tokio/sync.

Other updates:

  • The solver now uses ProjectRead::version and ProjectRead::usage
    summaries, allowing index candidates to be solved from versions.json
    without downloading archives.
  • The protocol docs now distinguish project persistence from project
    retirement, and version persistence from version retirement.
  • .meta.json created serialization is centralized and now emits
    second precision with a Z suffix.
  • Java/Python bindings handle the resolver's new fallible construction;
    Java enables Tokio drivers; Python test setup isolates uv from outer
    conda environments.
  • Tests cover discovery, IRI normalization, index read validation,
    digest/size drift, index project status filtering, yanked/removed
    behavior, publish discovery, and lock/sync round-trips for index-backed
    projects.

@consideRatio consideRatio marked this pull request as draft April 15, 2026 13:33
@consideRatio consideRatio force-pushed the pr/client-read-new-index branch 3 times, most recently from 4f04d45 to 4b14059 Compare April 16, 2026 07:05
@consideRatio consideRatio force-pushed the pr/client-read-new-index branch 12 times, most recently from ef6f5e0 to 3d43a8f Compare April 20, 2026 08:38
@consideRatio consideRatio force-pushed the pr/client-read-new-index branch 4 times, most recently from 6ee8561 to de55a9e Compare April 20, 2026 12:08
@consideRatio consideRatio marked this pull request as ready for review April 20, 2026 12:17
@andrius-puksta-sensmetry
Copy link
Copy Markdown
Collaborator

Impressive...

@consideRatio

This comment was marked as resolved.

Comment thread docs/src/index-protocol.md Outdated
Comment thread docs/src/index-protocol.md Outdated
Comment thread docs/src/index-protocol.md Outdated
Comment thread docs/src/index-protocol.md
Comment thread docs/src/index-protocol.md Outdated
Comment thread docs/src/index-protocol.md Outdated
Comment thread docs/src/index-protocol.md
Comment thread docs/src/index-protocol.md Outdated
Comment thread docs/src/index-protocol.md Outdated
Comment thread docs/src/index-protocol.md Outdated
Copy link
Copy Markdown
Collaborator

@andrius-puksta-sensmetry andrius-puksta-sensmetry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a start...

Comment thread core/src/commands/lock.rs Outdated
Comment thread core/src/commands/lock.rs Outdated
Comment thread core/src/commands/lock.rs Outdated
Comment thread core/src/commands/lock.rs Outdated
Comment thread core/src/commands/lock.rs Outdated
Comment thread core/src/lock.rs Outdated
Comment thread core/src/lock.rs Outdated
Comment thread core/src/lock.rs Outdated
Comment thread core/src/lock.rs Outdated
Comment thread core/src/purl.rs Outdated
Minimal kpar fixtures should put `.project.json` and `.meta.json` at the
archive root. Using a synthetic `root/` directory makes these fixtures look like
they are testing root guessing rather than the ordinary minimal archive shape.

Update the remaining minimal kpar builders and comments to use root-level
metadata files. Fixtures that intentionally exercise root guessing keep their
own non-root directory names.

Signed-off-by: Erik Sundell <erik.sundell+2025@sensmetry.com>
Comment thread sysand/tests/cli_lock.rs Outdated
Comment thread sysand/tests/cli_lock.rs Outdated
The index-backed lock tests registered mocks that were either unasserted or had
implicit request counts. That made accidental index enumeration and skipped
network paths hard to spot.

Give those mocks exact expectations and assert them. In particular, pin direct
IRI locking to avoid `index.json` enumeration and pin lock-time archive handling
so `lock` records advertised kpar metadata without downloading the kpar.

Signed-off-by: Erik Sundell <erik.sundell+2025@sensmetry.com>
Several tests kept mockito mocks alive without declaring the
expected request count. That made accidental extra or missing requests
harder to spot in review.

Add explicit expectations to the straightforward single-call mocks,
assert the previously underscore-bound mocks, and keep negative mocks
in the index cache test to pin the lazy get_project behavior.

Signed-off-by: Erik Sundell <erik.sundell+2025@sensmetry.com>
The mock_project helper used lower-bound expectations for legacy
HTTP-source fixtures. The observed request counts are deterministic, so
make each caller pass the exact project and metadata probe counts instead.

Signed-off-by: Erik Sundell <erik.sundell+2025@sensmetry.com>
Index entry project info and metadata are fetched with try_join!.
When one leg returns a hard 404, the other future may be canceled before
its HTTP request is dispatched.

Keep exact assertions on the failing endpoint, but allow the sibling
mock to be touched at most once and document why that count is not
pinned exactly.

Signed-off-by: Erik Sundell <erik.sundell+2025@sensmetry.com>
Comment thread core/src/project/reqwest_kpar_download_tests.rs Outdated
Comment thread core/src/env/index.rs Outdated
consideRatio and others added 2 commits May 5, 2026 15:00
Co-authored-by: Andrius Pukšta <andrius.puksta@sensmetry.com>
Signed-off-by: Erik Sundell <erik.i.sundell@gmail.com>
The PR added several Rust tests and helpers with test_ prefixes.
Repository test naming avoids that prefix for newly added test cases.

Rename the new cases and index-test helpers while leaving older
pre-existing test_ names untouched.

Signed-off-by: Erik Sundell <erik.sundell+2025@sensmetry.com>
Comment thread core/src/env/index.rs Outdated
Co-authored-by: Andrius Pukšta <andrius.puksta@sensmetry.com>
Signed-off-by: Erik Sundell <erik.i.sundell@gmail.com>
Comment thread core/src/solve/pubgrub.rs Outdated
Comment thread core/src/env/index_tests.rs Outdated
consideRatio and others added 2 commits May 6, 2026 08:59
Co-authored-by: Andrius Pukšta <andrius.puksta@sensmetry.com>
Signed-off-by: Erik Sundell <erik.i.sundell@gmail.com>
Index `versions.json` entries are advertised in descending SemVer order.
The solver still sorts candidates before choosing the highest allowed
version, but the old pre-sort `.rev()` was inherited from ascending
`versions.txt` ordering and now works against the advertised order.

Signed-off-by: Erik Sundell <erik.sundell+2025@sensmetry.com>
Comment thread docs/src/index-protocol.md Outdated
Co-authored-by: Andrius Pukšta <andrius.puksta@sensmetry.com>
Signed-off-by: Erik Sundell <erik.i.sundell@gmail.com>
Comment thread docs/src/index-protocol.md Outdated
Co-authored-by: Andrius Pukšta <andrius.puksta@sensmetry.com>
Signed-off-by: Erik Sundell <erik.i.sundell@gmail.com>
Comment thread docs/src/index-protocol.md
Copy link
Copy Markdown
Collaborator

@andrius-puksta-sensmetry andrius-puksta-sensmetry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good.

@andrius-puksta-sensmetry andrius-puksta-sensmetry merged commit 6f5aa0f into sensmetry:main May 6, 2026
49 checks passed
consideRatio added a commit that referenced this pull request May 6, 2026
The client now reads remote indexes through the static-file protocol
documented in `docs/src/index-protocol.md`, with the publish-side API
scope split into `docs/src/index-api-protocol.md`.

This breaks compatibility with the old HTTP registry layout and with
`0.3` registry-shaped lockfiles. Users must regenerate lockfiles, and
index servers must serve the new discovery, `index.json`, `versions.json`,
and per-version file layout.

Wire-format changes (reads):
- `<discovery-root>/sysand-index-config.json` is fetched lazily on first
  index use to resolve `index_root` and `api_root`. A 404 defaults both
  roots to the discovery root; other non-2xx responses are hard errors.
- `index_root` / `api_root` must be absolute HTTP(S) base URLs without
  userinfo. Relative URLs, credentials in URLs, and non-base paths are
  rejected.
- `<index_root>/index.json` is now project enumeration. Each `projects`
  entry carries `iri` and optional `status`; omitted `status` is
  equivalent to `available`.
- `index.json` project `status` supports `available` and `removed`.
  List-all operations filter removed projects, while direct resolution of
  a specific IRI still goes through that project's `versions.json`.
- Per-project `versions.json` replaces the old `versions.txt` list. Each
  entry carries `version`, `usage`, `project_digest`, `kpar_size`,
  `kpar_digest`, and optional `status`.
- `versions.json` entries are validated at ingest: SemVer only, no build
  metadata, descending precedence order, no duplicates, and lowercase
  `sha256:<64-hex>` digests.
- `versions.json` status supports `available`, `yanked`, and `removed`.
  New resolution filters to available versions; direct reads of removed
  versions fail distinctly; yanked versions remain readable for pinned
  lockfiles.
- Per-version layout becomes `<project>/<version>/.project.json`,
  `.meta.json`, and `project.kpar`.
- IRI path mapping is `pkg:sysand/<publisher>/<name>` →
  `<publisher>/<name>/`; all other IRIs use
  `_iri/<sha256(normalized_iri)>/`.
- Non-canonical `pkg:sysand/...` IRIs are hard-rejected through the new
  `purl` validation path, including
  `InterchangeProjectUsageRaw::validate`.
- `project_digest` is verified before `.project.json` / `.meta.json` are
  exposed. `kpar_digest` and `kpar_size` are enforced during streamed
  archive download.

Wire-format changes (publish):
- `sysand publish --index` now treats the configured URL as the discovery
  root, performs mandatory discovery with the configured
  `HTTPAuthentication` policy, and posts to `<api_root>/v1/upload`.
- Publish still supports direct API-root URLs because a missing discovery
  document defaults `api_root` to the configured URL.
- Publish credentials are matched after discovery against the actual
  upload URL. Discovery can therefore be auth-gated while upload remains
  bearer-only.
- Publish payload preparation now runs before network access, rejects
  SemVer build metadata, enforces the KPAR size limit, and sends
  archive-integrity metadata including the KPAR SHA-256 digest.

Lock/sync format changes:
- Lockfile format bumps from `0.3` to `0.4`; old registry-shaped
  lockfiles are rejected with a regenerate message.
- `Source::Registry` is renamed to `Source::Index`.
- New `Source::IndexKpar` records `index_kpar`, `index_kpar_size`, and
  `index_kpar_digest`, preserving the raw-archive integrity tripwire from
  `lock` to `sync`.
- `sync` can now install index-backed KPARs directly from the lockfile and
  verifies the recorded size/digest without re-reading `versions.json`.
- Lockfile validation now distinguishes canonical project digest format
  from index KPAR digest format and rejects uppercase index KPAR digests
  before canonicalization.

Public Rust API:
- New modules: `env::discovery`, `env::index`, `project::index_entry`,
  and `purl`. Removed: `env::reqwest_http::HTTPEnvironmentAsync`.
- `IndexEnvironmentAsync` replaces the old HTTP registry environment and
  lazily resolves discovery endpoints.
- `CombinedResolver` / `CombinedProjectStorage` terminology changes from
  `Registry` to `Index`.
- `standard_index_resolver` and `standard_resolver` now return `Result`
  because discovery-root shape validation can fail.
- `ProjectRead` / `ProjectReadAsync` wrappers must explicitly forward
  `get_info`, `get_meta`, `version`, `usage`, and
  `checksum_canonical_hex`; the derive macro now forwards these methods.
- `CanonicalizationError::map_project_read` is added for wrapper error
  forwarding.
- `ReqwestKparDownloadedProject` now accepts optional expected KPAR
  digest/size in `new` and `new_guess_root`, verifies them during
  download, and exposes `ensure_downloaded_verified` /
  `is_downloaded_and_verified`. Builder setters like
  `with_expected_sha256_hex` / `with_expected_size` were removed.
- `ReqwestKparDownloadedError` gains `DigestMismatch` and `SizeMismatch`.
- `InterchangeProjectValidationError::MalformedSysandPurl` is added.
- `core/Cargo.toml` adds optional `idna` under networking and enables
  `tokio/sync`.

Other updates:
- The solver now uses `ProjectRead::version` and `ProjectRead::usage`
  summaries, allowing index candidates to be solved from `versions.json`
  without downloading archives.
- The protocol docs now distinguish project persistence from project
  retirement, and version persistence from version retirement.
- `.meta.json` `created` serialization is centralized and now emits
  second precision with a `Z` suffix.
- Java/Python bindings handle the resolver's new fallible construction;
  Java enables Tokio drivers; Python test setup isolates `uv` from outer
  conda environments.
- Tests cover discovery, IRI normalization, index read validation,
  digest/size drift, index project status filtering, yanked/removed
  behavior, publish discovery, and lock/sync round-trips for index-backed
  projects.

Co-authored-by: Andrius Pukšta <andrius.puksta@sensmetry.com>
Co-authored-by: Jonas Pukšta <146448971+Jonas-Puksta-Sensmetry@users.noreply.github.com>
Signed-off-by: Erik Sundell <erik.sundell+2025@sensmetry.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Change client's index reading behavior (breaking)

3 participants