feat: affiliates + preferences modules v0.7.0 (closes #21, #22) #25

Merged
pyr0ball merged 12 commits from feature/affiliates-module into main 2026-04-04 19:14:25 -07:00
Owner

Summary

  • circuitforge_core.affiliateswrap_url() with eBay EPN + Amazon Associates builders, per-retailer disclosure copy, injectable preference resolution for opt-out + BYOK (closes #21)
  • circuitforge_core.preferencesLocalFileStore (YAML), get_user_preference / set_user_preference public helpers, PreferenceStore protocol for future Heimdall cloud backend (closes #22 self-hosted path)
  • Snipe migrated: _affiliate_url() removed, wrap_url(retailer="ebay") wired (snipe feature/affiliate-links)

Resolution order

wrap_url() follows the design doc priority:

  1. User opted out → plain URL
  2. BYOK id set (Premium, requires user_id) → user's affiliate id
  3. CF env var set → CF's affiliate id
  4. Nothing configured → plain URL

Architecture notes

  • get_preference is an injected callable — module has zero hard dependency on Heimdall or any storage backend
  • PreferenceStore protocol enables backend swap (Heimdall#5 → cloud backend) without touching callers
  • _DEFAULT_STORE lives in preferences/store.py for reliable monkeypatching in tests
  • _REGISTRY is module-global; future test isolation can add a snapshot/restore fixture if needed

Test plan

  • pytest tests/test_affiliates/ tests/test_preferences.py -v — 352 tests pass
  • pytest tests/ -v — no regressions across full suite
  • Env var EBAY_AFFILIATE_CAMPAIGN_ID unset → plain URLs returned
  • Env var set → EPN params appended to eBay listing URLs
  • Env var AMAZON_ASSOCIATES_TAG set → tag= merged into Amazon URLs
  • Opted-out user (via get_preference) → plain URL regardless of env var
  • BYOK id set → user's id used instead of CF's
  • Anonymous (user_id=None) with BYOK configured → CF's id used
  • Snipe: search results unchanged; _affiliate_url() no longer in codebase
  • Affiliate links design: circuitforge-plans/shared/2026-04-04-affiliate-links-design.md
  • Heimdall#5: user_preferences column (cloud backend unblocked when that lands)
  • cf-core #24: MIT/BSL 1.1 split (this PR's affiliates module belongs in MIT layer)
## Summary - `circuitforge_core.affiliates` — `wrap_url()` with eBay EPN + Amazon Associates builders, per-retailer disclosure copy, injectable preference resolution for opt-out + BYOK (closes #21) - `circuitforge_core.preferences` — `LocalFileStore` (YAML), `get_user_preference` / `set_user_preference` public helpers, `PreferenceStore` protocol for future Heimdall cloud backend (closes #22 self-hosted path) - Snipe migrated: `_affiliate_url()` removed, `wrap_url(retailer="ebay")` wired (snipe `feature/affiliate-links`) ## Resolution order `wrap_url()` follows the design doc priority: 1. User opted out → plain URL 2. BYOK id set (Premium, requires `user_id`) → user's affiliate id 3. CF env var set → CF's affiliate id 4. Nothing configured → plain URL ## Architecture notes - `get_preference` is an injected callable — module has zero hard dependency on Heimdall or any storage backend - `PreferenceStore` protocol enables backend swap (Heimdall#5 → cloud backend) without touching callers - `_DEFAULT_STORE` lives in `preferences/store.py` for reliable monkeypatching in tests - `_REGISTRY` is module-global; future test isolation can add a snapshot/restore fixture if needed ## Test plan - [ ] `pytest tests/test_affiliates/ tests/test_preferences.py -v` — 352 tests pass - [ ] `pytest tests/ -v` — no regressions across full suite - [ ] Env var `EBAY_AFFILIATE_CAMPAIGN_ID` unset → plain URLs returned - [ ] Env var set → EPN params appended to eBay listing URLs - [ ] Env var `AMAZON_ASSOCIATES_TAG` set → `tag=` merged into Amazon URLs - [ ] Opted-out user (via `get_preference`) → plain URL regardless of env var - [ ] BYOK id set → user's id used instead of CF's - [ ] Anonymous (`user_id=None`) with BYOK configured → CF's id used - [ ] Snipe: search results unchanged; `_affiliate_url()` no longer in codebase ## Related - Affiliate links design: `circuitforge-plans/shared/2026-04-04-affiliate-links-design.md` - Heimdall#5: `user_preferences` column (cloud backend unblocked when that lands) - cf-core #24: MIT/BSL 1.1 split (this PR's affiliates module belongs in MIT layer)
pyr0ball added 12 commits 2026-04-04 18:35:02 -07:00
LLMRouter env-var auto-config:
- No llm.yaml required — auto-configures from ANTHROPIC_API_KEY,
  OPENAI_API_KEY, or OLLAMA_HOST on first use
- Bare-metal self-hosters can run any CF product with just env vars
- Falls back to FileNotFoundError with actionable message only when
  no env vars are set either

CFOrchClient auth:
- Reads CF_LICENSE_KEY env var (or explicit api_key param)
- Sends Authorization: Bearer <key> on all allocation/release requests
- Required for the hosted public coordinator; no-op for local deployments

HeimdallAuthMiddleware (new):
- FastAPI middleware for cf-orch coordinator
- Enabled by HEIMDALL_URL env var; self-hosted deployments skip it
- 5-min TTL cache (matching Kiwi cloud session) keeps Heimdall off the
  per-allocation hot path
- /api/health exempt; free-tier keys rejected with 403 + reason
- 13 tests covering cache TTL, tier ranking, and middleware gating
Dockerfile.orch — multi-mode image (coordinator | agent):
- coordinator: runs cf-orch coordinator on $CF_ORCH_PORT (default 7700)
- agent: connects to $CF_COORDINATOR_URL, serves $CF_AGENT_GPU_IDS

.forgejo/workflows/docker.yml — publishes on every vN.N.N tag:
- ghcr.io/circuit-forge/cf-orch:latest
- ghcr.io/circuit-forge/cf-orch:vX.Y.Z
- Layer cache via GHA cache backend

Closes #19. Bumps to v0.6.0.
pyr0ball merged commit c1e825c06a into main 2026-04-04 19:14:25 -07:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Circuit-Forge/circuitforge-core#25
No description provided.