Commit Graph
3 Commits
Author SHA1 Message Date
Cesar de la Vega 5dc22ec2d5 Ingest audiences config topic (#7390)
* docs: design audiences config topic ingestion

* feat: ingest audiences config topic

* chore: remove planning artifacts

* feat: add audiences config provider
2026-08-11 15:29:22 +02:00
Facundo MenzellaandClaude Opus 5 9380458788 Ingest the checkpoints config topic (#7370)
* Ingest the checkpoints config topic

Adds the `checkpoints` topic to the remote-config read facade, parsing models for
a checkpoint's ordered rules, and a CheckpointsConfigProvider to read them.

Rules are parsed only, not evaluated. Items are keyed by checkpoint name, so a
checkpoint resolves with a single blobData read: no topic-index scan, and
blobData already resolves a blob whether its bytes arrived inlined with the
config response or have to be downloaded, and already invalidates on an identity
change. This matches the split WorkflowsConfigProvider uses, where an item's
content is metadata and its blob is the payload.

Schema follows the "Storing and serving checkpoint rules" proposal: rules arrive
already in priority order, and a rule carries an audience reference rather than
an inline predicate (the audience's own predicate is expected in a separate
topic).

Parsing is tolerant, since the proposal notes the schema may still shift before
V1: unknown fields are ignored and a malformed rule is skipped rather than
failing the checkpoint.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSMmcX6kq7C4Q6sKYsnCpN

* Add the checkpoints files to RevenueCat.xcodeproj

Danger flagged the legacy project as out of sync: the new files were added via
Tuist/SPM, which doesn't touch it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSMmcX6kq7C4Q6sKYsnCpN

* Prune obvious comments

Drops comments restating the code, the Foundation snake_case explanation (the
keys are literal in the code and pinned by a test), and the AnyObject constraint
on CheckpointsConfigProviderType, which nothing needed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSMmcX6kq7C4Q6sKYsnCpN

* Fix the topic name, require an audience, drop the frequency cap

The backend serves this topic as `checkpoint_rules`, not `checkpoints`
(khepri #23594 registers `TopicName('checkpoint_rules')`), so we were reading a
topic that doesn't exist.

An absent audience no longer means "targets everyone": the rule is skipped
instead, matching `audience_id NOT NULL` in the schema. It's typed `Int` because
the publisher serves `rule.audience_id`, a BigInteger.

Frequency capping isn't used yet, so parsing it only adds ways to fail.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSMmcX6kq7C4Q6sKYsnCpN

* Decode checkpoint rules through the topic manager's typed helper

Follows Rick's review: the models are Decodable and read through
blobData(for:itemKey:as:) instead of hand-parsing AnyDecodable, with the
tolerance kept in the Codable implementation.

Two behaviour changes fall out of using JSONDecoder.default. Its date strategy
throws on an unparseable date, so a schedule bound that's present but invalid now
drops the whole rule instead of reading as nil, which had widened the window
(Bugbot and vegaro both flagged this). And a rule that fails to decode for any
reason is skipped by SkippableRule rather than failing its checkpoint.

CheckpointRuleSet no longer carries `identifier`: it's the topic item key, which
the caller already passes in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSMmcX6kq7C4Q6sKYsnCpN

* Type the audience reference as a public id

khepri now resolves the rule's internal audience and workflow ids to their
rc_public_ids before serving them (khepri #23594), so `audience` is a string like
`workflow_id`, not the BigInteger the first draft of that publisher sent.

That PR also emits UTC-marked timestamps and only publishes active or scheduled
rules, so the schedule parsing and the fail-closed date handling here now line up
with what's served.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSMmcX6kq7C4Q6sKYsnCpN

* Drop the schedule, it isn't V1

Same reasoning as the frequency cap: parsing a field nothing reads only adds ways
to fail.

Note for whoever wires up evaluation: khepri publishes rules in `scheduled` state
as well as `active`, so the window has to be parsed and honoured then, or a rule
will fire before its start date.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSMmcX6kq7C4Q6sKYsnCpN

* Drop rules with an empty audience or workflow reference

The synthesized Decodable accepted an empty string where the hand-written parser
had treated empty the same as missing, so those rules would have reached workflow
resolution instead of being dropped at ingest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSMmcX6kq7C4Q6sKYsnCpN

* Pin the checkpoints topic wire name

The provider doc still said `checkpoints` after the rename, and no test pinned
the literal: every fixture derives the key from the enum, so a wrong wire name
would read as an absent topic forever without failing anything.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSMmcX6kq7C4Q6sKYsnCpN

* Read the audience as `audience_id` and drop the checkpoint id

Both from vegaro's review, matching what Android is doing: the backend is
renaming the rule's `audience` key to `audience_id`, which convertFromSnakeCase
maps for free, and the checkpoint's own id is unused and easy to confuse with the
item key.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FSMmcX6kq7C4Q6sKYsnCpN

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 09:03:09 +00:00
Rick 5fe329ff64 feat(remote-config): add APIs for reading topic/blob data from RemoteConfigManager (#7134)
* feat(remote-config): add read facade for blob data

* docs(remote-config): document read facade APIs

* refactor(remote-config): clarify refresh waiter continuations

* docs(remote-config): document manager refresh state

* fix(remote-config): update purchases test manager mock

* fix(remote-config): read facade cache data off caller executor

* fix(remote-config): avoid async work in nimble autoclosures

* fix(remote-config): honor disabled state after topic reads

* fix(remote-config): avoid async unwrap autoclosures

* test(remote-config): fix disabled blob read assertion

* refactor(remote-config): move topic store protocol near disk cache

* fix(remote-config): stop reads after manager closes

* fix(remote-config): avoid repeated read refreshes

* fix(remote-config): guard committed reads against cache clears

* test(remote-config): make stale read tests deterministic
2026-07-06 09:34:12 +02:00