Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions docs/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ with a `default` arm rather than an exhaustive match, and do not assume the
constants shipped in any given SDK tag are the complete set. The same rule
applies to any future open vocabulary added to `v1`.

`FetchMarkersRequest.episode_numbering` and `SubmitMarkerRequest.episode_numbering`
are open too. Hosts send an external ID key, today `tvdb` or `tmdb`, or leave the
field empty when they cannot tell. A marker provider treats a value it does not
recognize like an empty one.

`DownloadProgress.phase` runs the other way: plugins send it and the host reads
it. The current values are `queued`, `downloading`, `paused`, `stalled`,
`importing`, and `import_blocked`. Hosts treat a value they do not recognize as
Expand Down Expand Up @@ -160,6 +165,20 @@ newer host stored could reject it. An absent wording, or any empty value in it,
means the host's own neutral words. Plugins can therefore declare wording
without raising their minimum host version.

`RequestRouterDescriptor.api_key_optional` and `base_url_optional` (added in
v0.26.0) are plain booleans, so they have no presence: unset, or an absent
descriptor, keeps the behaviour hosts had before the flags existed. The host
skips a connection without an API key, and its admin form asks for a key and a
base URL. Hosts that predate the flags drop them as unknown fields when they
install a plugin archive, so a plugin that sets `api_key_optional` gets
requests without a key only from a host that reads it. Prairie reads a bare
plugin binary's manifest strictly, so a Prairie host that predates the flags
refuses to install a binary that declares them; install it from an archive
there (Prairie-Server/prairie-server#2436). Hosts read the flags from the capability
metadata stored when the plugin was installed or updated. A host built on an
older SDK stored that metadata without them, so they take effect after the
plugin is next updated on a host that reads them.

A season-scoped `GetImagesRequest` is a scope, not a guarantee. Plugins that
can filter by season should do so, and plugins should populate
`ImageRecord.season_number` whenever the season is known. Hosts must bucket and
Expand All @@ -172,6 +191,17 @@ verify images by the per-image field rather than assume a filtered response.
- A plugin binary should return the same manifest shape that Prairie installs, except that binaries may compute their checksum dynamically at runtime.
- From this version, `convert.DecodeCapability` ignores fields and enum values it does not know, so a server node built on this SDK or later, in a mixed-version cluster sharing one database, still loads capability metadata written by a newer SDK; it only loses the parts added after its own SDK version. Nodes built on an earlier SDK decode strictly and reject such metadata, so upgrade every node past them before installing plugins that publish newer fields.

### Watch-sync batch size

`WatchSyncProviderDescriptor.max_batch_size` accepts 1 to 1000 from v0.27.0;
earlier releases accept 1 to 100. The value bounds every `ApplyEvents` call
(watched history, watchlist, favorites, and ratings) and sets the page size a
host asks for in `ListRemoteState`, so a plugin that raises it should handle
batches and pages of that size for every flow it declares. Manifest validation runs in the host when it
installs or loads a plugin and in the catalog tooling when it publishes one, so
a plugin that declares more than 100 needs both built on v0.27.0 or later. An
older host refuses to install or load it.

## Go Support

The supported public authoring path today is Go-only.
Expand Down
181 changes: 125 additions & 56 deletions pkg/pluginproto/prairie/plugin/v1/marker_provider.pb.go

Large diffs are not rendered by default.

64 changes: 64 additions & 0 deletions pkg/pluginproto/prairie/plugin/v1/marker_provider_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package pluginv1

import (
"testing"

"google.golang.org/protobuf/proto"
)

func TestMarkerRequests_EpisodeIdentityFieldNumbers(t *testing.T) {
assertFieldNumber(t, &FetchMarkersRequest{}, "episode_external_ids", 7)
assertFieldNumber(t, &FetchMarkersRequest{}, "episode_title", 8)
assertFieldNumber(t, &SubmitMarkerRequest{}, "episode_external_ids", 10)
assertFieldNumber(t, &SubmitMarkerRequest{}, "episode_title", 11)
assertFieldNumber(t, &FetchMarkersRequest{}, "episode_numbering", 9)
assertFieldNumber(t, &SubmitMarkerRequest{}, "episode_numbering", 12)
}

// A host that predates the episode fields sends requests without them; the
// plugin must see the series identity unchanged and empty episode fields.
func TestFetchMarkersRequest_OldPayloadDecodesWithoutEpisodeIdentity(t *testing.T) {
oldPayload, err := proto.Marshal(&FetchMarkersRequest{
ItemType: "episode",
ExternalIds: &MarkerExternalIDs{TmdbId: "1399", TvdbId: "121361"},
SeasonNumber: 1,
EpisodeNumber: 2,
})
if err != nil {
t.Fatalf("marshal old-style payload: %v", err)
}
var decoded FetchMarkersRequest
if err := proto.Unmarshal(oldPayload, &decoded); err != nil {
t.Fatalf("unmarshal old-style payload: %v", err)
}
if decoded.GetExternalIds().GetTvdbId() != "121361" || decoded.GetSeasonNumber() != 1 || decoded.GetEpisodeNumber() != 2 {
t.Fatalf("decoded series identity = %+v", &decoded)
}
if decoded.GetEpisodeExternalIds().GetTvdbId() != "" || decoded.GetEpisodeTitle() != "" || decoded.GetEpisodeNumbering() != "" {
t.Fatalf("episode identity = %q/%q/%q, want empty", decoded.GetEpisodeExternalIds().GetTvdbId(), decoded.GetEpisodeTitle(), decoded.GetEpisodeNumbering())
}
}

func TestSubmitMarkerRequest_EpisodeIdentityRoundTrips(t *testing.T) {
data, err := proto.Marshal(&SubmitMarkerRequest{
ItemType: "episode",
ExternalIds: &MarkerExternalIDs{TvdbId: "121361"},
SeasonNumber: 1,
EpisodeNumber: 1,
Segment: "intro",
EpisodeExternalIds: &MarkerExternalIDs{TvdbId: "3254641", TmdbId: "63056"},
EpisodeTitle: "Winter Is Coming",
EpisodeNumbering: "tvdb",
})
if err != nil {
t.Fatalf("marshal: %v", err)
}
var decoded SubmitMarkerRequest
if err := proto.Unmarshal(data, &decoded); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if decoded.GetEpisodeExternalIds().GetTvdbId() != "3254641" || decoded.GetEpisodeExternalIds().GetTmdbId() != "63056" ||
decoded.GetEpisodeTitle() != "Winter Is Coming" || decoded.GetEpisodeNumbering() != "tvdb" {
t.Fatalf("decoded episode identity = %+v", &decoded)
}
}
Loading
Loading