Skip to content

Expose per-feed static GTFS hashes before the multi-feed fingerprint replaces staticGtfsHash #1907

Open

Description

@0xaboomar

Problem

#1846 added staticGtfsHash to /api/v2/metadata.json so Watchdog can tell whether it is validating realtime data against the same static bundle Maglev serves. Watchdog downloads the same feed, takes the SHA-256 of the zip bytes, and compares it with staticGtfsHash. If they differ, it re-downloads. If they still differ after that, it reports that either its feed URL is wrong or the backend is running on outdated data.

The multi-feed spec in #1811 changes what that value means. staticGtfsHash reads import_metadata.file_hash. Under section 6 ("Import pipeline"), step 3, that column becomes a dataset fingerprint: a SHA-256 over the ordered import_feeds rows plus the hash of the stop consolidation file. Section 4.3 also notes that single-feed deployments get a new ETag on upgrade.

Once that lands, Watchdog can no longer use staticGtfsHash:

  • Even with one feed, the value won't equal the SHA-256 of the zip, so every Maglev server would look out of sync.
  • Watchdog can't compute the fingerprint itself. It doesn't have the feed ids, the source URL or path Maglev used, each feed's default agency, the stop consolidation file, or the exact format the rows are combined in before hashing.
  • In a multi-agency region, one combined hash can't tell Watchdog which feed is out of date.

The spec doesn't mention staticGtfsHash, so nothing currently stops its meaning from changing under the same field name.

Proposed solution

Phase C step 3 already plans to list the feeds in /metadata and the health endpoint. Each entry in that list should include the SHA-256 of that feed's zip, so clients can compare per feed:

{
  "staticGtfsFeeds": [
    { "id": "kcm", "source": "https://example.com/kcm.zip", "sha256": "aac8f1d4..." },
    { "id": "st", "source": "https://example.com/st.zip", "sha256": "5b21e09c..." }
  ]
}

The field names are only an example. What matters is that sha256 is the hash of the zip bytes as downloaded, before gtfstidy or any other processing, which is the same value the import already computes for each feed. Watchdog then compares the set of hashes it downloaded with the set Maglev lists, for one feed or many, and can say which feed is out of date.

For staticGtfsHash itself, either:

  • keep it as the raw zip SHA-256 when there is exactly one feed, or
  • move the fingerprint to a separate field (e.g. staticGtfsFingerprint) and document that it isn't a file hash.

Acceptance criteria

References

Activity

  1. Enigma044 commented on Oct 31, 2026

    @Enigma044
    Contributor

    I'd like to work on this. I've started with the single-feed case: a staticGtfsFeeds list on metadata and health, built from the stored import metadata, with the existing staticGtfsHash unchanged. The per-feed list with ids depends on Phase C of #1811, so I'd extend it after that merges.

  2. Ahmedhossamdev commented on Oct 31, 2026

    @Ahmedhossamdev
    Member

    This depends on Phase C of #1811 (multi-feed import). Step 3 of that phase adds the per-feed metadata, lists the feeds in /metadata and the health endpoint, and switches import_metadata.file_hash to the dataset fingerprint. The per-feed sha256 should be added there, taken from import_feeds.hash, so the code for this issue waits until Phase C step 3.

    Until then, a single-feed staticGtfsFeeds list would only repeat staticGtfsHash and would need rework once feed ids and import_feeds exist, so let's hold off on it. The spec additions (per-feed hashes in Phase C step 3 and the meaning of staticGtfsHash after the change) can go into #1811 in the meantime.

  3. Ahmedhossamdev commented on Oct 31, 2026

    @Ahmedhossamdev
    Member

    @Enigma044 thanks for opening #1910. I've converted it to draft: the staticGtfsFeeds list waits for Phase C step 3 of #1811, as noted above, and the gtfstidy hash fix should move to its own issue and PR, coordinated with #1676. Details are in #1910 (comment).

  4. Ahmedhossamdev commented on Oct 31, 2026

    @Ahmedhossamdev
    Member

    @Enigma044 one more thing, and thanks again for the interest. This issue was already assigned to me when #1910 was opened. In future, please check the assignee before starting work, and if an issue is assigned to someone else, ask on the issue first. CONTRIBUTING asks that a PR links an issue a maintainer has triaged or assigned to you. That way two people don't spend time on the same change, and the assignee can say whether the work is ready to start (here it depends on Phase C). If you'd like to take the gtfstidy hash fix, open an issue for it and I'm happy to assign it to you.

  5. Enigma044 commented on Oct 31, 2026

    @Enigma044
    Contributor

    Thanks, understood. I'll wait for Phase C step 3 for the feed list. I should have checked the assignee first. I've opened a separate issue for the gtfstidy hash fix: #1911. I'll ask for it to be assigned to me before opening a PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions