Skip to content

Add partner portal and contextual product promotions - #59

Draft
abhi1693 wants to merge 46 commits into
masterfrom
feat/partnership
Draft

abhi1693 wants to merge 46 commits into
masterfrom
feat/partnership

Conversation

@abhi1693

@abhi1693 abhi1693 commented Oct 2, 2026 •

Copy link
Copy Markdown
Owner

What changes and why?

Add the partner portal, private product catalog pipeline, and automatic contextual promotions. Products now inherit their partner account's settings through import connections, including future approved imports. Operators can configure a partner once instead of manually classifying each product as a placement or ad.

  • Free partners can receive relevant ordinary recommendations. Explicit paid campaigns establish sponsorship independently of partnership tier or product pricing. Campaigns support whole-catalog, selected-product and technology/topic targeting, finite delivery dates, priority, surfaces and optional daily impression budgets.
  • Administration provides account promotion defaults, searchable account inventory, product exclusions and campaign performance. Pauses, exclusions, withdrawn listings and expired qualification override commercial settings. Campaign priority breaks equal-relevance ties.
  • Bounded background preparation matches verified product evidence with practical articles, approved taxonomy topics, reader follows and prepared interests. Validated AI evaluations remain authoritative for identical inputs. Reader requests use prepared matches and recheck current policy; unrelated products never fill empty slots.
  • The website and both extensions show at most one ordinary recommendation and one clearly labelled sponsored placement, with a factual reason, HTTPS destination, hiding, cooldown and local exposure limits. Visible articles take priority over background feeds.
  • Fresh anonymous signed receipts, durable campaign reservations and atomic event deduplication connect delivery to existing impression/click reports. Automatic account/product/campaign attribution is immutable. Existing manually managed assets and imported measurements remain available for historical reporting.
  • The separate partner portal reports account benefits and performance over UTC reporting periods. Zitadel roles and issuer/subject memberships govern access; partners remain read-only. Reader, partner and administration authentication boundaries remain separate.
  • The partner Products & ads table supports ascending/descending Name, Impressions, Clicks and CTR sorting across the complete paginated report. Sort order and page survive navigation and reloads; missing measurements stay last. The Type column is removed.

Validation

  • Passed 55 focused portal/reporting/tracking checks, including 23 new database sorting regression cases, plus 47 partner frontend tests, lint/types, production build and desktop/mobile sorting browser checks. Coverage includes all four fields in both directions, global pagination, combined metrics, missing/zero values, deterministic ties, membership revocation, saved-page recovery, and URL/reload/navigation persistence.
  • Passed backend lint, formatting, types and workspace/API version checks; passed Compose configuration and signing-key isolation checks.
  • Passed the complete backend unit CI command: 3,831 tests, no failures or skips, and 68.30% combined coverage above the unchanged 67.60% floor.
  • Passed 126 relevant disposable PostgreSQL/Redis integration cases, plus two final concurrency regressions proving authoritative negative evaluations survive busy product/article rows. Coverage includes migration preservation, ownership, policy changes, stale matches, campaign caps, tracking deduplication, existing partner workflows and reader interests.
  • Passed the full reader suite (1,337 tests), admin suite (706 tests), partner portal suite (47 tests) and extension suite (32 tests). Final changes additionally passed 72 focused reader tests and 20 focused admin tests.
  • Passed frontend lint/type checks, production reader/admin builds and both Chrome/Edge package builds. Real built-runtime placement and tracking browser journeys passed for web, Chrome and Edge, including signed-in article priority, credential isolation, hide persistence, exposure caps and narrow layouts. Desktop/mobile admin campaign workflows passed.
  • Passed the CI-pinned full-history Gitleaks scan after replacing disposable fixture literals with generated test keys and recording exact reviewed historical false positives. Revalidated 55 API tests and all 19 placement integration tests.
  • Passed pre-commit file checks; the affected-project commands were run directly to avoid duplicating the complete suites in the hook.
  • Verified the running dev database is at 0028, affected services are healthy, the running matching code is current, and a real dev topic query returns an organic product with a signed receipt and Cache-Control: no-store.
  • Hosted required CI, CodeQL, ARM64 images and the six-sample API performance comparison must pass on this new head. The previous head failed feed-pagination/source-feed API performance comparisons; local functional checks do not replace that gate. Production rollout and extension store publication are outside this implementation.

UI screenshots remain untracked under reports/partner/ and reports/partner-placements/. Native GitHub attachment upload is unavailable in this environment, so this PR has no newly uploaded screenshots.

Rollout notes

Apply partnership migrations 0024–0028 after master's published source-logo migration 0023. Migration 0028 preserves catalog/accounts/memberships/metrics and introduces inherited defaults, campaigns, exclusions, prepared matches and delivery reservations. Existing accounts enable ordinary recommendations and automatic inclusion; no campaign activates automatically. Review account policy before enabling production delivery. Keep a database backup before downgrade: new policy/matching/campaign data is removed, while original asset/metric data remains.

Configure the same server-only DEVFEED_PARTNER_TRACKING_KEY in the public API and user API with at least 32 random ASCII characters. Compose passes it only to those backends; empty disables delivery and collection. Local .env was configured and remains ignored. Run the scheduler and Redis, and deploy services requiring schema 0028 together. Campaign budgets and event reports use the receipt's UTC issuance day; outstanding receipts reserve capacity until their one-hour expiry. Browser exposure history is local to each website/extension installation.

The partner portal needs its dedicated Zitadel client, partner origin, secure cookies, DNS/TLS and issuer/subject account memberships. Roles are bounded by session lifetime; immediate role revocation also requires session invalidation. This feature records paid agreements but does not collect payments, revenue or conversions.

See promotion policy and rollout and partner portal setup.

Ready for review

  • The description explains the change and validation results, including remaining gaps.
  • Relevant documentation and regression tests are updated.
  • The revised head passes hosted CI required and CodeQL, with review conversations resolved.

Comment thread apps/admin/tests/browser/partner-tools.mjs Fixed
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

PR reader API performance

Result: INCONCLUSIVE

3 inconclusive · 5 within threshold. Zero request failures across all six runs.

Base af3220ca445f474e9d82f0363229b82d8d5812fb → PR 904e920fd9c1550d0ddf845ede0a0ef34c6b053a

Three independent runs per revision, each on a fresh GitHub-hosted ARM64 runner. 16 users, 60 seconds/run, 1,000 synthetic articles, cache off, five PgBouncer slots.

Latency comparison

All latency values are in milliseconds; lower is better. Values are medians of three runs.

Route Base p95 PR p95 Change Result
All requests 94 100 +6 (+6.4%) Within threshold
Latest feed 120 140 +20 (+16.7%) Within threshold
Feed pagination 85 99 +14 (+16.5%) Within threshold
Article detail 68 81 +13 (+19.1%) Within threshold
Source feed 88 97 +9 (+10.2%) Within threshold
Topic feed 73 100 +27 (+37.0%) Inconclusive
Feed filters 79 91 +12 (+15.2%) Within threshold
Sources 46 41 -5 (-10.9%) Inconclusive
Topics 71 48 -23 (-32.4%) Inconclusive
Additional latency and throughput metrics

Each cell shows base → PR. Latencies are in milliseconds.

Endpoint p50 p99 Requests/s
Aggregated 31 → 30 130 → 140 30.10 → 30.08
/v1/feed [latest] 44 → 46 160 → 180 8.56 → 8.72
/v1/feed [page] 29 → 28 120 → 130 3.88 → 4.00
/v1/articles/[id] 18 → 19 110 → 110 6.42 → 6.01
/v1/feed [source] 29 → 29 120 → 130 1.94 → 1.87
/v1/feed [topic] 28 → 28 100 → 110 1.78 → 1.78
/v1/feed/options 23 → 23 99 → 120 2.64 → 2.49
/v1/sources 13 → 11 86 → 95 2.64 → 2.49
/v1/topics 14 → 13 93 → 81 2.64 → 2.49
Individual runs
Run Requests Failures Exit code
Base 1 1692 0 0
Base 2 1680 0 0
Base 3 1714 0 0
PR 1 1652 0 0
PR 2 1694 0 0
PR 3 1679 0 0
How to interpret this report

Regressed / Improved: a p95 regression needs >20% and >20 ms increase in the medians and at least two head samples versus the base median. Improvements use the inverse threshold. Within threshold means the change did not meet both thresholds.

Inconclusive: a within-revision p95 range >35% of its median and >20 ms is too noisy for a confident latency verdict.

Inconclusive latency comparisons are non-blocking: the check passes without claiming an improvement. Confirmed regressions and test/report errors fail the PR gate. Throughput is descriptive: paced users do not measure maximum capacity. This uncached public API test does not establish production health, browser performance or worker capacity. Separate runners can differ in hardware or host load; repetitions and noise checks reduce but cannot eliminate that uncertainty.

Full HTML/CSV reports and logs · attempt 1

@abhi1693 abhi1693 changed the title Add private partner catalog and article relevance evaluation Add partner portal with memberships and performance analytics Oct 7, 2026
@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Backend unit coverage

FAIL · minimum combined coverage 67.60%.

Tests: 3971 passed, 1 failed, 0 skipped. Source files: 316.

Workspace Lines Branches Combined
All backend code 71.60% 53.41% 67.66%
apps/admin-api 61.30% 30.00% 55.88%
apps/aggregator 70.72% 53.75% 66.87%
apps/api 75.29% 60.67% 72.68%
apps/article-enrichment-worker 80.00% 50.00% 71.43%
apps/cli 68.93% 42.93% 65.00%
apps/images-worker 80.00% 50.00% 71.43%
apps/mcp 83.05% 73.88% 81.34%
apps/notifications 83.33% 76.67% 81.75%
apps/partner-api 78.64% 50.00% 73.69%
apps/search-indexer 85.48% 85.71% 85.53%
apps/source-discovery-worker 80.00% 50.00% 71.43%
apps/user-api 60.08% 34.02% 55.24%
packages/core 74.23% 57.34% 70.17%
packages/http 88.59% 80.85% 86.96%

Unit coverage includes every backend workspace. PostgreSQL/Redis integration tests run separately.

Commit: 904e920 · HTML, JSON, XML reports and job logs · attempt 1

@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Lighthouse · ⏸️ Not measured

Tests did not pass for this commit.
Full reports and logs · 904e920 · Attempt 1

Preserve public profile freshness and anonymous partner tracking in the extension transport, and retain both reader parity suites. Move unpublished partner migrations to 0024–0027 after the published source-logo migration. Align partner packages and regenerated admin contracts with workspace version 0.0.52.

Validated affected-project checks directly: Python lint, formatting, types, versions and 3711 unit tests (68.41% coverage); populated master-schema upgrade and 74 partner integration tests; admin, reader and partner lint, tests and production builds; extension types, tests and builds; web, Chrome and Edge browser journeys and history checks. Chrome sign-in passed an isolated rerun after the initial concurrent run timed out.
@sonarqubecloud

Copy link
Copy Markdown

@abhi1693 abhi1693 changed the title Add partner portal with memberships and performance analytics Add partner portal and contextual product promotions Oct 11, 2026
const section = url.searchParams.get("section");
return {
query: url.searchParams.get("q") ?? "",
sections: section ? { [section]: sections[section] } : sections,
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.

2 participants