Skip to content

Conversation

@dmitrytrager
Copy link
Collaborator

Implement GET /api/v1/beacons/manifest to build and return the full manifest JSON (language, region, tags, providers with nested topics and files, totals, checksum). Implement HEAD support with If-None-Match (304 Not Modified) and If-Match (412 Precondition Failed) for lightweight change detection during sync.

Manifest versioning uses lazy evaluation: content checksum is compared with stored value on the beacon, and the version is incremented only when content actually changes. Conditional requests use the stored version directly, avoiding a full manifest build for 304/412 responses.

What Issue Does This PR Cover, If Any?

Resolves
#566
#567
#573

What Changed? And Why Did It Change?

  • Manifest controller with change detection and corresponding response statuses
  • Manifest building logic, saving version and checksum inside beacon
  • Files and assignments tracking logic that will update manifest data for beacon once triggered

How Has This Been Tested?

Rspec

Please Provide Screenshots

Additional Comments

For changed associations (such as topic/providers) rebuild is triggered from callbacks now.
This logic should be later moved into controllers/mutators.

dmitrytrager and others added 2 commits February 3, 2026 15:45
Implement GET /api/v1/beacons/manifest to build and return the full
manifest JSON (language, region, tags, providers with nested topics
and files, totals, checksum). Implement HEAD support with If-None-Match
(304 Not Modified) and If-Match (412 Precondition Failed) for
lightweight change detection during sync.

Manifest versioning uses lazy evaluation: content checksum is compared
with stored value on the beacon, and the version is incremented only
when content actually changes. Conditional requests use the stored
version directly, avoiding a full manifest build for 304/412 responses.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Introduce Beacons::RebuildManifestJob and wire it into Topics::Mutator
(create, update, archive, unarchive, destroy), BeaconTopic/BeaconProvider
join model callbacks, and SynchronizeCognatesOnTopicsJob so that beacon
manifests stay current without waiting for a GET request.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

# During sync, beacon sends If-Match with its current version.
# If the manifest changed since sync started, return 412 so beacon aborts and restarts.
if stale_by_match?(stored_etag)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Couple of checks here to protect from unnecessary rebuild

belongs_to :beacon
belongs_to :provider

after_commit :rebuild_beacon_manifest, on: [ :create, :destroy ]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

When we add controller/mutator for managing association, move this logic there

belongs_to :beacon
belongs_to :topic

after_commit :rebuild_beacon_manifest, on: [ :create, :destroy ]
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

When we add controller/mutator for managing association, move this logic there

Copy link
Contributor

Choose a reason for hiding this comment

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

Could this logic go in a concern shared by the two models?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It will not stay in models. It will go to controller/mutator when we add managing associations

@dmitrytrager dmitrytrager marked this pull request as ready for review February 3, 2026 15:57
@seanmarcia
Copy link
Member

Some nice things we might want to store for the admins are some statistics on the current deployed manifest as well as the upcoming version. So we can have some nice summary facts like:
This beacon is providing data with the following configurations:
Language: Spanish
Region: Europe
Tags: ...
Providers:
Total Files on Beacon: 1522
Total Size of Files: 40gb
On the next update this beacon will receive:
78 files for a total of 1600 files and the new total size of the beacon will be 64gb
(Or something like that)

@dmitrytrager
Copy link
Collaborator Author

We can store manifest itself inside beacon model. It will help to show total files number or size.
It won't help with diff however, we can only see full content this way.
Working with diffs will make things much more complicated.

I will save manifest into beacon

@seanmarcia
Copy link
Member

How about storing statistics on the most recent deployed manifest? Size, Number of files, etc? Then we can just do some math and compare.

dmitrytrager and others added 2 commits February 4, 2026 12:03
Save the manifest content (without version/checksum metadata) as jsonb
on the beacon record so admins can inspect the full manifest state.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Rotate manifest_data into previous_manifest_data before saving
new content, so admins can compare current and previous manifests.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@dmitrytrager
Copy link
Collaborator Author

Added manifest_data and previous_manifest_data to beacon

::Beacons::ManifestBuilder.new(Current.beacon)
end

def stale_by_match?(etag)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering if the naming could be a little clearer here, maybe version_mismatches?.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

But it is important to underline here that version can become stale during process. And later that is can be outdated. Just match/mismatch does not cover that these are different checks. But I am open to better naming for sure

Copy link
Contributor

Choose a reason for hiding this comment

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

Could go with sync_version_stale? and cached_version_fresh?.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done

if_match != etag
end

def fresh_by_none_match?(etag)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, could we go for something like version_matches?.

belongs_to :beacon
belongs_to :topic

after_commit :rebuild_beacon_manifest, on: [ :create, :destroy ]
Copy link
Contributor

Choose a reason for hiding this comment

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

Could this logic go in a concern shared by the two models?

Copy link
Contributor

@oatkins8 oatkins8 left a comment

Choose a reason for hiding this comment

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

Looks good 👍

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants