Skip to content

Worker: revocations, install counts, Play verification; client publish() - #229

Merged
HereLiesAz merged 2 commits into
mainfrom
claude/amazing-fermi-3o92qn-gaps
Sep 27, 2026
Merged

HereLiesAz merged 2 commits into
mainfrom
claude/amazing-fermi-3o92qn-gaps

Conversation

@HereLiesAz

@HereLiesAz HereLiesAz commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

This fills the Worker's remaining Repository API gaps and adds publish() to the client.

Revocations (§ 5)

  • A moderator can yank a version from /moderation (Dismiss / Yank on each report), or through POST /api/admin/revocations and DELETE /api/admin/revocations/{id}/{version}.
  • POST /api/reports/{id}/resolve records the decision. A yank uses the report's version, or the version served now if the report named none.
  • A yanked version:
    • appears on /revocations (supports ?since=);
    • drops out of /packages and /api/packages;
    • shows yanked: true in its detail;
    • answers 404 not_found on download.

Install counts (§ 8)

  • Every full 200 download, free or paid, carries azphalt-report-token: 32 random bytes, stored with only the package and version, and deleted after 30 days if unspent. 206 responses carry none.
  • POST /installs spends tokens and receipts per spec. Rejections are counted, not fatal.
  • installs and uninstalls appear on the package summaries.
  • ⚠️ Behavior change: free downloads are now streamed from the git catalog instead of 302-redirected, because a redirect's headers never reach the client that follows it. Range passes through. Worker egress on the free plan costs nothing.

Play (§ 7)

  • src/play.ts implements a real verifier:
    • it signs a service-account JWT (RS256 over WebCrypto) and trades it for an OAuth token;
    • it reads purchases.products and acknowledges unacknowledged purchases.
  • The subject is play-account:<obfuscatedExternalAccountId>, falling back to play-order:<orderId>.
  • The product must match the listing's new optional playProductId, which defaults to the package id.
  • Responses:
    • 402 when Google does not recognise the purchase;
    • 502 when Google cannot be reached or our credentials are refused;
    • 501 for subscription listings;
    • 501 until PLAY_PACKAGE_NAME (var, store.azphalt.storefront) and PLAY_SERVICE_ACCOUNT_JSON (secret) are set.

Client

  • RepositoryClient.publish(azp) returns PublishPending or PublishLive, and throws PublishError with status, code and details.

Docs

  • The privacy policy's install-counts and Play sections, and the GitHub bullet, now describe what the store actually does.
  • The terms header comment and the Worker README are updated.
  • Changesets: @azphalt/repository-client minor (public); the Worker and React apps minor (private), in separate files so neither is a mixed changeset.

Validation

  • Worker tests: 32 (7 new). They run the real DO over node:sqlite and stub GitHub raw and Google. The OAuth assertion's RS256 signature is verified against the generated key.
  • Client tests: 17.
  • pnpm test and pnpm -r typecheck pass across the workspace.
  • In real workerd (wrangler dev), the following worked end to end: feed, yank, detail, listing exclusion, report resolve, installs and Play 501. That run exercised the ALTER TABLE migration on DO SQLite.

Needs from you for Play: a service account with View financial data and Manage orders on the store app, then wrangler secret put PLAY_SERVICE_ACCOUNT_JSON and the PLAY_PACKAGE_NAME var.

Not in this PR: the Worker has no POST /updates (§ 6) yet, and store-app subscriptions via Play remain unsupported.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QwYWVPse8spRrfMMcuTJPv


Generated by Claude Code

Summary by Sourcery

Complete the Worker's remaining repository APIs and add client-side package publishing.

New Features:

  • Add repository support for version revocations, moderation decisions, install and uninstall reporting, and Google Play purchase verification.
  • Add RepositoryClient.publish() with pending-review and published results plus structured publish errors.

Bug Fixes:

  • Stream free package downloads through the Worker so full-download report tokens reach clients while preserving range responses.
  • Prevent revoked versions from appearing in listings or being downloaded.

Enhancements:

  • Expose install and uninstall tallies in package summaries and add configurable Play product identifiers.
  • Update moderation workflows, privacy and terms content, Worker documentation, and publishing package documentation.

Deployment:

  • Document the Play service-account secret and package-name configuration required to enable purchase verification.

Documentation:

  • Document revocation, install reporting, Play verification, streamed free downloads, and client publishing behavior.

Tests:

  • Add Worker coverage for revocations, moderation resolution, install reporting, download tokens, and Play purchase verification.
  • Add repository-client coverage for publish success and structured failure responses.

Chores:

  • Add minor changesets for the repository client, Worker, and storefront React application.

- Moderators yank a version from /moderation or the admin API. It lands on
  /revocations, leaves listings, shows yanked in detail, and its download 404s.
- Full downloads carry azphalt-report-token. POST /installs counts installs and
  uninstalls against tokens and receipts. Free downloads are streamed from the
  git catalog so the token can ride along.
- POST /entitlements/play verifies one-time purchases with the Android
  Publisher API (service-account JWT, RS256 over WebCrypto) and issues the
  store-signed entitlement. It answers 501 until configured.
- @azphalt/repository-client gains publish().
- Privacy policy, terms header and Worker README updated to match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QwYWVPse8spRrfMMcuTJPv
@vercel

vercel Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
azphalt Error Error Sep 27, 2026 6:25am UTC

@sourcery-ai

sourcery-ai Bot commented Sep 27, 2026

Copy link
Copy Markdown

Reviewer's Guide

The PR closes the Worker’s remaining repository API gaps by adding durable revocation and moderation workflows, anonymous install-count reporting, and Google Play purchase verification, while changing free downloads to Worker streaming so report tokens reach clients. It also adds typed client publish() support, expands integration coverage, and updates documentation and release metadata.

Sequence diagram for streamed downloads and install reporting

sequenceDiagram
    participant Client
    participant Worker
    participant GitCatalog
    participant DurableObject

    Client->>Worker: GET package download
    Worker->>DurableObject: POST /report-token
    DurableObject-->>Worker: 32-byte token
    Worker->>GitCatalog: Fetch package bytes
    GitCatalog-->>Worker: 200 or 206 response
    Worker-->>Client: Stream bytes with azphalt-report-token on 200
    Client->>Worker: POST /installs
    Worker->>DurableObject: Redeem token or receipt
    DurableObject-->>Worker: accepted or rejected
    Worker-->>Client: Install report result
Loading

Sequence diagram for moderator revocation workflow

sequenceDiagram
    actor Moderator
    participant Storefront
    participant Worker
    participant DurableObject
    participant RepositoryClient

    Moderator->>Storefront: Yank report
    Storefront->>Worker: POST /api/reports/{id}/resolve
    Worker->>DurableObject: PUT /report/{id}/resolution
    DurableObject-->>Worker: Report version
    Worker->>DurableObject: PUT /revocation/{packageId}/{version}
    DurableObject-->>Worker: Revocation
    Worker-->>Storefront: Resolved report and revocation
    RepositoryClient->>Worker: GET /revocations?since=
    Worker->>DurableObject: GET /revocations
    DurableObject-->>Worker: Yanked versions
    Worker-->>RepositoryClient: Revocation feed
Loading

Sequence diagram for Google Play purchase verification

sequenceDiagram
    participant App
    participant Worker
    participant GoogleOAuth
    participant GooglePlay
    participant DurableObject

    App->>Worker: POST /entitlements/play
    Worker->>GoogleOAuth: JWT assertion signed with RS256
    GoogleOAuth-->>Worker: OAuth access token
    Worker->>GooglePlay: Verify product purchase token
    GooglePlay-->>Worker: Purchase state and order identity
    opt Unacknowledged purchase
        Worker->>GooglePlay: Acknowledge purchase
    end
    Worker->>DurableObject: issueEntitlement
    DurableObject-->>Worker: Store-signed entitlement
    Worker-->>App: Entitlement or 402/502/501
Loading

Sequence diagram for client package publishing

sequenceDiagram
    participant Publisher
    participant RepositoryClient
    participant Repository

    Publisher->>RepositoryClient: publish(azp)
    RepositoryClient->>Repository: POST /packages
    alt 201 Created
        Repository-->>RepositoryClient: Live package
        RepositoryClient-->>Publisher: PublishLive
    else 202 Accepted
        Repository-->>RepositoryClient: Pending review
        RepositoryClient-->>Publisher: PublishPending
    else Refused
        Repository-->>RepositoryClient: Error envelope
        RepositoryClient-->>Publisher: PublishError
    end
Loading

File-Level Changes

Change Details Files
Implemented repository revocation workflows and moderation decisions across the Worker and storefront.
  • Added admin revoke/unrevoke endpoints and report resolution with dismiss/yank actions.
  • Persisted revocations and report decisions in Durable Object SQLite, including migration of existing report tables.
  • Added revocation feeds, yanked detail metadata, listing exclusion, and download rejection.
  • Added moderation UI controls and updated report API types/client calls.
apps/storefront-worker/src/index.ts
apps/storefront-react/src/App.tsx
apps/storefront-react/src/api.ts
apps/storefront-worker/test/gaps.test.ts
Added anonymous install and uninstall reporting backed by download-issued single-use credentials.
  • Minted 32-byte tokens for full downloads and omitted them from ranged responses.
  • Streamed free Git-backed downloads through the Worker while preserving range handling and upstream metadata.
  • Added token/receipt redemption, rejection accounting, 30-day token cleanup, and package install tallies.
  • Persisted reporting tables and exposed install counts in package summaries.
apps/storefront-worker/src/index.ts
apps/storefront-worker/test/gaps.test.ts
apps/storefront-worker/README.md
apps/storefront-react/public/privacy.html
apps/storefront-react/public/terms.html
Implemented Google Play one-time purchase verification and entitlement issuance.
  • Added service-account parsing, RS256 JWT signing with WebCrypto, OAuth token exchange, and token caching.
  • Verified product purchases, mapped Google responses to 402/502, acknowledged unacknowledged purchases, and derived stable entitlement subjects.
  • Added configuration for Play package name and service-account credentials while explicitly rejecting subscriptions.
apps/storefront-worker/src/play.ts
apps/storefront-worker/src/index.ts
apps/storefront-worker/test/gaps.test.ts
apps/storefront-worker/README.md
Added client-side package publishing support with typed success and failure results.
  • Posted raw AZP bytes to the repository publish endpoint.
  • Returned typed pending-review or published results for 202/201 responses.
  • Added PublishError with HTTP status, repository error code, message, and problem details.
packages/repository-client/src/index.ts
packages/repository-client/test/publish.test.ts
packages/repository-client/readme.md
.changeset/repository-client-publish.md
Updated public documentation and release metadata to reflect the completed Worker API coverage.
  • Documented install counts, Play verification, streaming downloads, moderation routes, and required configuration.
  • Updated privacy and terms language to describe actual data handling and GitHub access.
  • Added separate public/private minor changesets for the client, Worker, and React app.
apps/storefront-react/public/privacy.html
apps/storefront-react/public/terms.html
apps/storefront-worker/README.md
.changeset/worker-gaps.md

Possibly linked issues

  • #repository-api-marketplace-host-gaps: Link: the PR directly implements the issue’s revocation-feed gap, including yanks, polling, listing removal, and download blocking.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Owner Author

github-advanced-security fails here for a reason outside this PR. It is GitHub's Copilot security agent (COPILOT_AGENT_MODEL: sweagent-capi:claude-opus-5), and Copilot rejects that model. It failed the same way on #226 and #227, before this branch existed.

Nothing in the repository runs it, so no code change can fix it. It stops once Copilot's code review / security agent is turned off in the repo settings. The OpenCode review check that replaces it (#228) passed on this commit.


Generated by Claude Code

@HereLiesAz
HereLiesAz marked this pull request as ready for review September 27, 2026 06:24

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @HereLiesAz, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 2 days and 21 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@HereLiesAz
HereLiesAz merged commit 91415e7 into main Sep 27, 2026
3 of 4 checks passed
@HereLiesAz
HereLiesAz deleted the claude/amazing-fermi-3o92qn-gaps branch September 27, 2026 06:25

This branch had an error being deployed

1 failed deployment
Preview — f7cf6775 Deployed Sep 27, 2026 by vercel[bot]
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