Skip to content

Version Manifest (versions.json)

CLI-internal state

versions.json is the Portolan CLI's own state file — its single source of truth for version history, checksums, and sync state — not part of the published Portolan specification. Consumers of a published catalog never need to read it. This page documents the format the CLI writes and the compatibility contract its tests guard (tests/integration/test_versions_manifest_schema.py).

Each collection MUST include a versions.json file that tracks version history, asset checksums, and sync state.

Location

{collection_id}/versions.json

Versioning is per-collection, not per-item. When any item in a collection changes, the collection version increments.

Schema

{
  "spec_version": "1.0.0",
  "current_version": "2.1.0",
  "versions": [
    {
      "version": "2.1.0",
      "created": "2024-01-15T10:30:00Z",
      "breaking": false,
      "assets": {
        "districts/districts.parquet": {
          "sha256": "abc123def456...",
          "size_bytes": 1048576,
          "href": "boundaries/districts/districts.parquet"
        }
      },
      "changes": ["districts/districts.parquet"]
    }
  ]
}

Path Resolution

Asset keys and hrefs follow strict conventions to ensure push and pull can resolve files unambiguously.

Asset keys are scoped relative to the collection directory: - For single-file collections (no items): the filename alone (e.g., tunnels.parquet) - For collections with items: {item_id}/{filename} (e.g., districts/districts.parquet)

Asset hrefs are catalog-root-relative paths, enabling tools to resolve files via catalog_root / href: - For single-file collections: {collection_id}/{filename} (e.g., tunnels/tunnels.parquet) - For collections with items: {collection_id}/{item_id}/{filename} (e.g., boundaries/districts/districts.parquet)

The collection directory name MUST NOT appear in the asset key. The asset key is resolved relative to the collection directory, not the catalog root.

# Correct — asset key is collection-relative
"tunnels.parquet": {
  "href": "tunnels/tunnels.parquet"
}

# Incorrect — asset key duplicates the collection directory
"tunnels/tunnels.parquet": {
  "href": "tunnels/tunnels/tunnels.parquet"
}

Fields

Root Level

Field Type Required Description
spec_version string MUST Schema version for the versions.json format (currently "1.0.0")
current_version string | null MUST The latest version string, or null if no versions exist
versions array MUST List of version entries, oldest first

Version Entry

Field Type Required Description
version string MUST Semantic version string (e.g., "1.0.0")
created string MUST ISO 8601 timestamp in UTC (e.g., "2024-01-15T10:30:00Z")
breaking boolean MUST true if this version has breaking changes
assets object MUST Map of item-scoped asset keys to asset metadata
changes array MUST List of asset keys that changed in this version

Asset Entry

Field Type Required Description
sha256 string MUST SHA-256 checksum of the file content
size_bytes integer MUST File size in bytes
href string MUST Catalog-root-relative path to the asset (e.g., "boundaries/districts/districts.parquet")
source_path string MAY Relative path to the original source file (e.g., the GeoJSON converted to this GeoParquet)
source_mtime number MAY Unix timestamp of the source file when conversion occurred; used to detect when the source has changed
mtime number MAY Unix timestamp of the asset file itself; fast-path change detection
feature_count integer MAY Feature/row count (pixel count for rasters) when tracked; freshness heuristic
schema_fingerprint string MAY Hash of the asset schema when tracked; a change indicates a breaking schema change

Versioning Rules

Version Numbering

Versions SHOULD follow Semantic Versioning: - Major (X.0.0): Breaking changes (schema changes, column removals) - Minor (0.X.0): New features (new columns, new items) - Patch (0.0.X): Data updates (same schema, new data)

Breaking Changes

A change is considered breaking if consumers depending on the previous schema would fail: - Column removed or renamed - Column type changed - Geometry type changed - CRS changed

Adding new columns is not breaking.

Change Detection

A file is listed in changes if: - It's new (not in the previous version) - Its SHA-256 checksum differs from the previous version

Sync State

The versions.json file serves as the sync manifest: 1. Compare local versions.json against remote 2. Push files where local checksum differs from remote 3. Update remote versions.json after successful push

Example: Single-File Collection

A collection at tunnels/ with one GeoParquet file. The asset key is the filename alone, and the href is catalog-root-relative:

{
  "spec_version": "1.0.0",
  "current_version": "1.0.0",
  "versions": [
    {
      "version": "1.0.0",
      "created": "2024-01-15T10:30:00Z",
      "breaking": false,
      "assets": {
        "tunnels.parquet": {
          "sha256": "abc123...",
          "size_bytes": 1048576,
          "href": "tunnels/tunnels.parquet"
        }
      },
      "changes": ["tunnels.parquet"]
    }
  ]
}

Example: Collection with Items

A collection at boundaries/ with item subdirectories. The asset key includes the item ID, and the href includes both collection and item:

{
  "spec_version": "1.0.0",
  "current_version": "1.1.0",
  "versions": [
    {
      "version": "1.0.0",
      "created": "2024-01-01T00:00:00Z",
      "breaking": false,
      "assets": {
        "districts/districts.parquet": {
          "sha256": "abc123...",
          "size_bytes": 524288,
          "href": "boundaries/districts/districts.parquet"
        }
      },
      "changes": ["districts/districts.parquet"]
    },
    {
      "version": "1.1.0",
      "created": "2024-06-15T12:00:00Z",
      "breaking": false,
      "assets": {
        "districts/districts.parquet": {
          "sha256": "def456...",
          "size_bytes": 786432,
          "href": "boundaries/districts/districts.parquet"
        },
        "districts/districts.pmtiles": {
          "sha256": "ghi789...",
          "size_bytes": 262144,
          "href": "boundaries/districts/districts.pmtiles"
        }
      },
      "changes": ["districts/districts.parquet", "districts/districts.pmtiles"]
    }
  ]
}

In this example: - v1.0.0: Initial import with one parquet file - v1.1.0: Updated parquet data and added PMTiles derivative