Skip to content

Added agent payments for premium markdown URLs - #29840

Open
louisghost wants to merge 15 commits into
mainfrom
x402-mpp-markdown
Open

Added agent payments for premium markdown URLs#29840
louisghost wants to merge 15 commits into
mainfrom
x402-mpp-markdown

Conversation

@louisghost

@louisghost louisghost commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds pay-per-request Stripe Machine Payments for paid-members markdown (.md) URLs, so AI agents can unlock premium content without creating a member session.

  • MPP first (Tempo USDC + Shared Payment Tokens); x402 is a secondary adapter behind the same canHandle / challenge / fulfill boundary
  • One-shot unlock of markdown bytes for that request only — no tier grants, Portal changes, or content-gating changes
  • Explicit .md URLs only — Accept-header markdown, HTML theme views, and the Content API stay membership-gated
  • Enablement: labs machinePayments + llms_enabled + machine_payments_enabled + Stripe connected
  • Discovery: eligible paid posts appear in /llms.txt and get markdown alternate links when enabled
  • Durable Tempo deposit addresses (settings-backed) + machine_payment_events ledger after successful fulfill
  • Admin controls in Membership → Tiers (price + toggle), gated behind labs / Stripe / llms.txt

This supersedes the earlier exploration in #28291 with a Ghost-shaped architecture and product fences documented in ghost/core/core/server/services/machine-payments/README.md.

Test plan

  • Run migrations; confirm machine_payments_* settings and machine_payment_events table exist
  • Enable Labs → Machine payments; connect Stripe; keep llms.txt on; enable agent payments under Membership → Tiers
  • Publish a paid post; curl -i http://localhost:2368/<slug>.md returns 402 with a Payment challenge (not 403)
  • Confirm paid slug appears in /llms.txt when enabled, and free-members-only posts do not
  • Confirm public .md still returns 200; Accept-header markdown on paid HTML URLs is unchanged
  • Optional roundtrip: npx mppx@latest validate http://localhost:2368/<slug>.md (Stripe sandbox / testnet config)
  • cd ghost/core && pnpm run test:single test/unit/server/services/machine-payments/
  • Settings / schema snapshot tests updated and green

@github-actions github-actions Bot added the migration [pull request] Includes migration for review label Aug 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

It looks like this PR contains a migration 👀
Here's the checklist for reviewing migrations:

General requirements

  • ⚠️ Tested performance on staging database servers, as performance on local machines is not comparable to a production environment
  • Satisfies idempotency requirement (both up() and down())
  • Does not reference models
  • Filename is in the correct format (and correctly ordered)
  • Targets the next minor version
  • All code paths have appropriate log messages
  • Uses the correct utils
  • Contains a minimal changeset
  • Does not mix DDL/DML operations
  • Tested in MySQL and SQLite

Schema changes

  • Both schema change and related migration have been implemented
  • For index changes: has been performance tested for large tables
  • For new tables/columns: fields use the appropriate predefined field lengths
  • For new tables/columns: field names follow the appropriate conventions
  • Does not drop a non-alpha table outside of a major version

Data changes

  • Mass updates/inserts are batched appropriately
  • Does not loop over large tables/datasets
  • Defends against missing or invalid data
  • For settings updates: follows the appropriate guidelines

@nx-cloud

nx-cloud Bot commented Aug 10, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix

Ensure the fix-ci command is configured to always run in your CI pipeline to get automatic fixes in future runs. For more information, please see https://nx.dev/ci/features/self-healing-ci


View your CI Pipeline Execution ↗ for commit bc39c25

Command Status Duration Result
nx run @tryghost/admin:test:acceptance ✅ Succeeded 7m 38s View ↗
nx run ghost:test:ci:integration ✅ Succeeded 2m 19s View ↗
nx run ghost:test:integration ✅ Succeeded 2m 26s View ↗
nx run-many -t test:unit -p @tryghost/admin-x-f... ✅ Succeeded 4m 45s View ↗
nx run @tryghost/koenig-lexical:test:acceptance ✅ Succeeded 2m 32s View ↗
nx run ghost-admin:test ✅ Succeeded 3m 8s View ↗
nx run ghost:test:legacy ✅ Succeeded 2m 47s View ↗
nx run ghost:test:e2e ✅ Succeeded 2m 7s View ↗
Additional runs (8) ✅ Succeeded ... View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-08-10 18:31:47 UTC

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds machine-payment settings, feature flags, validation, Stripe and blockchain payment adapters, payment event storage, and admin controls. Paid Markdown requests can use payment challenges and fulfillment. LLMS discovery and Markdown alternate links now account for purchasable entries and machine-payment enablement. Tests and package metadata are updated.

Possibly related PRs

  • TryGhost/Ghost#29611: Both PRs add a private labs feature flag and corresponding Admin feature entry.

Suggested reviewers: erisds

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: agent payments for premium Markdown URLs.
Description check ✅ Passed The description accurately explains the payment flow, scope, enablement requirements, discovery behavior, and test plan.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch x402-mpp-markdown

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 16

🧹 Nitpick comments (6)
ghost/core/test/unit/server/services/machine-payments/adapters.test.js (1)

14-16: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Assert the approved contract address.

Line 15 accepts any hexadecimal value. A different valid address would pass the test and send payments to the wrong contract. Assert TEMPO_USDC equals the approved Tempo USDC address. You can retain a 20-byte format assertion as an additional check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ghost/core/test/unit/server/services/machine-payments/adapters.test.js`
around lines 14 - 16, Update the test case for TEMPO_USDC to assert equality
with the approved Tempo USDC contract address, rather than accepting any
hexadecimal address; retain the existing 20-byte format validation only as an
optional additional check.
ghost/core/core/server/services/machine-payments/stripe/deposit-address-store.js (2)

72-83: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Log the failure before you fall back.

The catch block discards the Stripe error. A credential problem, a permission problem, and a transient outage all look identical, and the code then issues a second Stripe call. Log the error at warning level.

🛠️ Proposed fix
+const logging = require('`@tryghost/logging`');
             } catch (err) {
                 // Fall through to PaymentIntent deposit mode.
+                logging.warn(err);
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ghost/core/core/server/services/machine-payments/stripe/deposit-address-store.js`
around lines 72 - 83, Update the catch block in the deposit-address creation
flow to log the caught Stripe error at warning level before falling through to
PaymentIntent deposit mode. Preserve the existing fallback behavior and include
enough context plus the original error details for diagnosis.

50-66: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Address creation stays on the request path and is only deduplicated per process.

getOrCreateAddress runs during a paid Markdown request, and #inflight deduplicates within a single Node process only. Multiple Ghost instances that start with an empty cache each create an address. Stripe recommends keeping deposit-address creation off the core request path: "You can create deposit addresses as often as you want, but we recommend that you keep these calls off your core request path."

Consider creating the address during boot or when an admin enables machine payments, and treat the request path as read-only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@ghost/core/core/server/services/machine-payments/stripe/deposit-address-store.js`
around lines 50 - 66, Move address creation out of getOrCreateAddress and into a
boot-time or machine-payments enablement initialization flow, ensuring it runs
before paid Markdown requests need the address. Make getOrCreateAddress
request-path read-only: return the cached machine_payments_deposit_address when
present and otherwise fail with the established unavailable-state behavior
instead of calling `#createAndPersist`. Preserve request-level inflight handling
only if it remains necessary for reads.
ghost/core/core/frontend/services/llms/service.js (1)

39-41: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Move the require to module scope.

isDiscoverable resolves ../../../shared/machine-payments on every entry. Hoist it next to the other imports unless a circular dependency forces the lazy require.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ghost/core/core/frontend/services/llms/service.js` around lines 39 - 41, Move
the machine-payments require from inside isDiscoverable to module scope
alongside the other imports, then reuse that module-level binding within
isDiscoverable. Only retain the lazy require if the change introduces a circular
dependency.
ghost/core/core/server/services/machine-payments/stripe/payment-recorder.js (1)

19-21: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add stripePaymentIntentId to the JSDoc.

Line 38 reads payment.stripePaymentIntentId, but the type on line 20 does not declare it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ghost/core/core/server/services/machine-payments/stripe/payment-recorder.js`
around lines 19 - 21, Add the optional stripePaymentIntentId property to the
payment parameter type in the JSDoc for the relevant payment-recording function,
matching the property accessed via payment.stripePaymentIntentId while
preserving the existing fields.
ghost/core/test/unit/server/services/machine-payments/service.test.js (1)

126-138: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the rejected-credential path.

#paymentCredentialErrorResponse maps a statusCode === 403 error to a 403 problem response and any other error to 503. No test exercises either branch. Add a case where mppAdapter.fulfill rejects.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ghost/core/test/unit/server/services/machine-payments/service.test.js` around
lines 126 - 138, Extend the machine-payments service tests around
challengeOrFulfill with a rejected-credential case by making mppAdapter.fulfill
reject with a 403-status error and asserting a 403 response, plus a non-403
rejection asserting 503. Reuse the existing service setup and request context
from the “returns 503 when no adapter can challenge” test to cover both branches
of `#paymentCredentialErrorResponse`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ghost/core/core/frontend/helpers/ghost_head.js`:
- Around line 28-33: Update isMachinePaymentsEnabled to reuse the shared
enablement logic from MachinePaymentsService.isEnabled or
settingsHelpers.isStripeConnected(), rather than checking for either Stripe
secret key directly. Preserve the existing labs and llms_enabled conditions
while ensuring the helper follows the same active-key-mode validation as the
service.

In
`@ghost/core/core/server/data/migrations/versions/6.58/2026-08-10-08-21-00-add-machine-payment-events-table.js`:
- Around line 5-14: The machine payment events table lacks an atomic idempotency
constraint. In the migration definition at
ghost/core/core/server/data/migrations/versions/6.58/2026-08-10-08-21-00-add-machine-payment-events-table.js
lines 5-14, add a composite unique constraint covering protocol and reference;
mirror the same constraint in the machine_payment_events schema declaration at
ghost/core/core/server/data/schema/schema.js lines 841-850.

In `@ghost/core/core/server/services/machine-payments/adapters/mpp-adapter.js`:
- Around line 47-64: Update MppAdapter.fulfill to require the expected
successful payment status before reading receipt fields or returning fulfillment
data; continue mapping status 402 to NoPermissionError, and reject every other
non-success status with an appropriate error. Only construct and return the
receipt-derived fulfillment after this validation.

In `@ghost/core/core/server/services/machine-payments/adapters/x402-adapter.js`:
- Around line 52-60: The x402 adapter’s payment reference must be stable across
retries; update the return logic around paymentResponse to derive reference from
immutable settlement data such as the on-chain transaction hash, and remove the
Date.now fallback. If no stable settlement reference is available, fail
fulfillment rather than returning a fabricated reference, while preserving the
existing payment-response header behavior.
- Around line 95-100: Update the route registration in the x402 adapter so the
handler uses the HTTP method derived from terms.method, matching the route built
earlier, instead of always registering GET. Preserve the existing response body,
status, headers, and app.fetch(request) behavior for all supported methods.
- Around line 4-10: Update formatPrice to restrict x402 pricing to
USD-denominated terms, rather than emitting unsupported non-USD strings.
Validate the currency before formatting and reject or otherwise prevent non-USD
values from entering the Base/ExactEvmScheme rail; preserve the existing USD
output format.
- Around line 63-93: Refactor the x402 adapter to construct and cache the Hono
resource server, facilitator, and ExactEvmScheme setup once rather than
rebuilding them inside `#dispatch`. Reuse that server across challenge() and
fulfill() dispatches, while preserving per-request route, terms, and
deposit-address handling as required; update the nearby comment to describe the
reusable-server behavior and remove any outdated signature concern.

In
`@ghost/core/core/server/services/machine-payments/events/machine-payment-event-repository.js`:
- Around line 20-42: Update MachinePaymentEventRepository.save to handle
concurrent duplicate inserts: preserve the existing findOne fast path, wrap the
Model.add call in unique-constraint error handling, and on that specific failure
re-read and return the existing row using the same protocol/reference criteria.
Do not suppress unrelated persistence errors; if no unique constraint exists,
retain the current behavior and rely on the lookup result.

In
`@ghost/core/core/server/services/machine-payments/events/machine-payment-event.js`:
- Around line 17-23: Update MachinePaymentEvent.create to validate that data
includes non-missing postId, amount, currency, protocol, method, and reference
before constructing the event. Reject invalid input immediately, while
preserving the existing timestamp fallback and successful construction path.

In `@ghost/core/core/server/services/machine-payments/index.js`:
- Around line 46-48: Update the comment immediately above X402Adapter
registration in the adapter setup to accurately state that x402 is always
registered as a second payment rail, while agents that do not support it ignore
it; remove the incorrect facilitator-config condition.

In `@ghost/core/core/server/services/machine-payments/pricing.js`:
- Around line 19-27: Update getTerms() to default machine_payments_amount only
when the setting is null or undefined, preserving an explicit zero. After
resolving the amount, call assertValidAmount(amount) before returning the terms
so invalid or nonnumeric values cannot reach the payment adapter; keep the
existing currency resolution unchanged.

In `@ghost/core/core/server/services/machine-payments/service.js`:
- Around line 133-178: Update `#handleFulfill` to record the payment immediately
after adapter.fulfill succeeds and before loadFullEntry can return the 403
response. Capture the value returned by paymentRecorder.record and use it as the
Stripe PaymentIntent ID when building the eventRepository.save payload, falling
back only when no recorder result is available; preserve the existing warning
behavior for recording failures.

In
`@ghost/core/core/server/services/machine-payments/stripe/deposit-address-store.js`:
- Around line 88-101: The `#createViaPaymentIntent` method should cancel the
temporary PaymentIntent after extracting the deposit address, preventing an
unused requires_action record from persisting. Reuse the existing configured
amount and currency symbols instead of hardcoding them in the PaymentIntent
creation, while preserving the address extraction flow.
- Around line 1-7: Update the workspace Stripe dependency/catalog from 8.222.0
to a Stripe Node.js version that supports stripe.crypto.depositAddresses and the
2026-05-27.preview API, then retain the STRIPE_MACHINE_PAYMENTS_API_VERSION
usage in the deposit-address implementation.

In `@ghost/core/core/server/services/machine-payments/stripe/payment-recorder.js`:
- Around line 31-48: Ensure non-USD terms are rejected consistently across both
payment paths: in
ghost/core/core/server/services/machine-payments/stripe/payment-recorder.js:31-48,
update the payment-intent creation in the payment recorder to use
payment.currency or reject unsupported currencies before the call; in
ghost/core/core/server/services/machine-payments/adapters/x402-adapter.js:4-10,
update formatPrice to produce only an x402-supported price format and refuse to
build challenges for unsupported currencies.

In `@ghost/core/test/unit/server/services/machine-payments/service.test.js`:
- Around line 17-19: Fix the labs stub setup in the relevant test fixture so
isSet itself is configured with withArgs('machinePayments').returns(true),
rather than assigning the filtered child stub; this must allow unexpected flag
names to return the default value. Add a test case covering the disabled
machinePayments flag and assert that isEnabled() returns false.

---

Nitpick comments:
In `@ghost/core/core/frontend/services/llms/service.js`:
- Around line 39-41: Move the machine-payments require from inside
isDiscoverable to module scope alongside the other imports, then reuse that
module-level binding within isDiscoverable. Only retain the lazy require if the
change introduces a circular dependency.

In
`@ghost/core/core/server/services/machine-payments/stripe/deposit-address-store.js`:
- Around line 72-83: Update the catch block in the deposit-address creation flow
to log the caught Stripe error at warning level before falling through to
PaymentIntent deposit mode. Preserve the existing fallback behavior and include
enough context plus the original error details for diagnosis.
- Around line 50-66: Move address creation out of getOrCreateAddress and into a
boot-time or machine-payments enablement initialization flow, ensuring it runs
before paid Markdown requests need the address. Make getOrCreateAddress
request-path read-only: return the cached machine_payments_deposit_address when
present and otherwise fail with the established unavailable-state behavior
instead of calling `#createAndPersist`. Preserve request-level inflight handling
only if it remains necessary for reads.

In `@ghost/core/core/server/services/machine-payments/stripe/payment-recorder.js`:
- Around line 19-21: Add the optional stripePaymentIntentId property to the
payment parameter type in the JSDoc for the relevant payment-recording function,
matching the property accessed via payment.stripePaymentIntentId while
preserving the existing fields.

In `@ghost/core/test/unit/server/services/machine-payments/adapters.test.js`:
- Around line 14-16: Update the test case for TEMPO_USDC to assert equality with
the approved Tempo USDC contract address, rather than accepting any hexadecimal
address; retain the existing 20-byte format validation only as an optional
additional check.

In `@ghost/core/test/unit/server/services/machine-payments/service.test.js`:
- Around line 126-138: Extend the machine-payments service tests around
challengeOrFulfill with a rejected-credential case by making mppAdapter.fulfill
reject with a 403-status error and asserting a 403 response, plus a non-403
rejection asserting 503. Reuse the existing service setup and request context
from the “returns 503 when no adapter can challenge” test to cover both branches
of `#paymentCredentialErrorResponse`.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c2632d91-bf1f-412e-be4b-923b901498ce

📥 Commits

Reviewing files that changed from the base of the PR and between f318505 and 555a779.

⛔ Files ignored due to path filters (2)
  • ghost/core/test/e2e-api/admin/__snapshots__/config.test.js.snap is excluded by !**/*.snap
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (47)
  • apps/admin-x-framework/src/api/settings.ts
  • apps/admin-x-framework/src/test/msw-utils.ts
  • apps/admin/src/settings/app/components/settings/advanced/labs/private-features.tsx
  • apps/admin/src/settings/app/components/settings/membership/tiers.tsx
  • apps/ember-admin/package.json
  • ghost/core/core/frontend/helpers/ghost_head.js
  • ghost/core/core/frontend/services/llms/service.js
  • ghost/core/core/frontend/services/routing/controllers/entry.ts
  • ghost/core/core/frontend/services/routing/controllers/entry/markdown.ts
  • ghost/core/core/frontend/web/site.js
  • ghost/core/core/server/api/endpoints/utils/serializers/input/settings.js
  • ghost/core/core/server/data/exporter/table-lists.js
  • ghost/core/core/server/data/migrations/versions/6.58/2026-08-10-08-20-50-add-machine-payments-settings.js
  • ghost/core/core/server/data/migrations/versions/6.58/2026-08-10-08-21-00-add-machine-payment-events-table.js
  • ghost/core/core/server/data/schema/default-settings/default-settings.json
  • ghost/core/core/server/data/schema/schema.js
  • ghost/core/core/server/models/index.js
  • ghost/core/core/server/models/machine-payment-event.js
  • ghost/core/core/server/models/settings.js
  • ghost/core/core/server/services/machine-payments/README.md
  • ghost/core/core/server/services/machine-payments/adapters/mpp-adapter.js
  • ghost/core/core/server/services/machine-payments/adapters/x402-adapter.js
  • ghost/core/core/server/services/machine-payments/content-loader.js
  • ghost/core/core/server/services/machine-payments/eligibility.js
  • ghost/core/core/server/services/machine-payments/events/machine-payment-event-repository.js
  • ghost/core/core/server/services/machine-payments/events/machine-payment-event.js
  • ghost/core/core/server/services/machine-payments/index.js
  • ghost/core/core/server/services/machine-payments/pricing.js
  • ghost/core/core/server/services/machine-payments/service.js
  • ghost/core/core/server/services/machine-payments/stripe/deposit-address-store.js
  • ghost/core/core/server/services/machine-payments/stripe/payment-recorder.js
  • ghost/core/core/shared/config/defaults.json
  • ghost/core/core/shared/labs.js
  • ghost/core/core/shared/machine-payments.js
  • ghost/core/package.json
  • ghost/core/test/e2e-api/admin/settings.test.js
  • ghost/core/test/integration/exporter/exporter.test.js
  • ghost/core/test/legacy/models/model-settings.test.js
  • ghost/core/test/unit/server/data/schema/integrity.test.js
  • ghost/core/test/unit/server/services/machine-payments/adapters.test.js
  • ghost/core/test/unit/server/services/machine-payments/content-loader.test.js
  • ghost/core/test/unit/server/services/machine-payments/deposit-address-store.test.js
  • ghost/core/test/unit/server/services/machine-payments/eligibility.test.js
  • ghost/core/test/unit/server/services/machine-payments/service.test.js
  • ghost/core/test/utils/fixtures/default-settings.json
  • packages/testing/test-data/src/fixtures/data/settings.ts
  • pnpm-workspace.yaml

Comment thread ghost/core/core/frontend/helpers/ghost_head.js Outdated
Comment thread ghost/core/core/server/services/machine-payments/service.js
@codecov

codecov Bot commented Aug 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.07407% with 458 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.35%. Comparing base (77f2685) to head (1d94b42).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
.../services/machine-payments/adapters/mpp-adapter.js 28.82% 121 Missing ⚠️
...e/core/server/services/machine-payments/service.js 68.00% 96 Missing ⚠️
...s/machine-payments/stripe/deposit-address-store.js 40.64% 92 Missing ⚠️
...services/machine-payments/adapters/x402-adapter.js 33.33% 80 Missing ⚠️
...rvices/machine-payments/stripe/payment-recorder.js 74.44% 23 Missing ⚠️
...ayments/events/machine-payment-event-repository.js 76.56% 15 Missing ⚠️
...end/services/routing/controllers/entry/markdown.ts 87.83% 7 Missing and 2 partials ⚠️
...server/services/machine-payments/content-loader.js 92.06% 5 Missing ⚠️
...s/machine-payments/events/machine-payment-event.js 86.48% 5 Missing ⚠️
...t/core/core/server/models/machine-payment-event.js 83.33% 3 Missing ⚠️
... and 4 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #29840      +/-   ##
==========================================
- Coverage   75.45%   75.35%   -0.11%     
==========================================
  Files        1605     1618      +13     
  Lines      140458   141794    +1336     
  Branches    17424    17565     +141     
==========================================
+ Hits       105988   106849     +861     
- Misses      33393    33864     +471     
- Partials     1077     1081       +4     
Flag Coverage Δ
admin-tests 55.97% <ø> (ø)
e2e-tests 77.47% <66.07%> (-0.14%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
ghost/core/test/unit/frontend/services/llms/service.test.js (1)

554-557: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Assert that the paid body remains absent.

The assertion proves that Paid Post includes the teaser and notice. It does not prove that the paid body is omitted. Add a negative assertion for a unique body marker from the paid fixture. Also assert that the excluded members-only body is absent when the fixture provides one.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ghost/core/test/unit/frontend/services/llms/service.test.js` around lines 554
- 557, Strengthen the assertions in the getLlmsFullTxt test by verifying that a
unique marker from the paid post body is absent, while retaining the existing
teaser and paywall notice checks. When the fixture includes an excluded
members-only body marker, add a corresponding negative assertion for it as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@ghost/core/test/unit/frontend/services/llms/service.test.js`:
- Around line 554-557: Strengthen the assertions in the getLlmsFullTxt test by
verifying that a unique marker from the paid post body is absent, while
retaining the existing teaser and paywall notice checks. When the fixture
includes an excluded members-only body marker, add a corresponding negative
assertion for it as well.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9cc2573e-79ae-4570-afb1-19b3b3065490

📥 Commits

Reviewing files that changed from the base of the PR and between 555a779 and 067b1e4.

⛔ Files ignored due to path filters (2)
  • ghost/core/test/e2e-api/admin/__snapshots__/settings.test.js.snap is excluded by !**/*.snap
  • ghost/core/test/unit/frontend/helpers/__snapshots__/ghost-head.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (6)
  • ghost/core/core/server/data/exporter/table-lists.js
  • ghost/core/test/e2e-api/admin/settings.test.js
  • ghost/core/test/integration/exporter/exporter.test.js
  • ghost/core/test/legacy/models/model-settings.test.js
  • ghost/core/test/unit/frontend/services/llms/service.test.js
  • ghost/core/test/unit/server/data/exporter/index.test.js
🚧 Files skipped from review as they are similar to previous changes (4)
  • ghost/core/core/server/data/exporter/table-lists.js
  • ghost/core/test/legacy/models/model-settings.test.js
  • ghost/core/test/e2e-api/admin/settings.test.js
  • ghost/core/test/integration/exporter/exporter.test.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
ghost/core/core/server/services/machine-payments/adapters/mpp-adapter.js (1)

55-61: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Require a 2xx status before accepting payment fulfillment.

Both adapters accept 1xx and 3xx responses as successful fulfillment. These statuses do not confirm payment success. A response with a receipt header can then grant paid content without a successful payment result.

  • ghost/core/core/server/services/machine-payments/adapters/mpp-adapter.js#L55-L61: reject statuses below 200 and statuses from 300 onward.
  • ghost/core/core/server/services/machine-payments/adapters/x402-adapter.js#L42-L52: reject statuses below 200 and statuses from 300 onward.
Proposed fix
-        if (!payment.status || payment.status >= 400) {
+        if (!payment.status || payment.status < 200 || payment.status >= 300) {
-        if (response.status === 402 || response.status >= 400) {
+        if (response.status < 200 || response.status >= 300) {
For the MPPX and `@x402` package versions declared by this repository, which HTTP response status ranges indicate a successfully fulfilled server-side payment?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ghost/core/core/server/services/machine-payments/adapters/mpp-adapter.js`
around lines 55 - 61, Require a 2xx payment status before accepting fulfillment:
update the status validation in mpp-adapter.js lines 55-61 and x402-adapter.js
lines 42-52 to reject statuses below 200 or at least 300, while preserving
receipt handling for valid responses.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@ghost/core/core/server/services/machine-payments/adapters/mpp-adapter.js`:
- Around line 55-61: Require a 2xx payment status before accepting fulfillment:
update the status validation in mpp-adapter.js lines 55-61 and x402-adapter.js
lines 42-52 to reject statuses below 200 or at least 300, while preserving
receipt handling for valid responses.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 19bdb55f-869e-48c0-92f5-9c933333881f

📥 Commits

Reviewing files that changed from the base of the PR and between 067b1e4 and e643bc9.

📒 Files selected for processing (18)
  • ghost/core/core/frontend/helpers/ghost_head.js
  • ghost/core/core/frontend/services/proxy.js
  • ghost/core/core/server/data/migrations/versions/6.58/2026-08-10-08-21-00-add-machine-payment-events-table.js
  • ghost/core/core/server/data/schema/schema.js
  • ghost/core/core/server/services/machine-payments/adapters/mpp-adapter.js
  • ghost/core/core/server/services/machine-payments/adapters/x402-adapter.js
  • ghost/core/core/server/services/machine-payments/events/machine-payment-event-repository.js
  • ghost/core/core/server/services/machine-payments/events/machine-payment-event.js
  • ghost/core/core/server/services/machine-payments/index.js
  • ghost/core/core/server/services/machine-payments/pricing.js
  • ghost/core/core/server/services/machine-payments/service.js
  • ghost/core/core/server/services/machine-payments/stripe/deposit-address-store.js
  • ghost/core/core/server/services/machine-payments/stripe/payment-recorder.js
  • ghost/core/core/shared/machine-payments.js
  • ghost/core/test/unit/server/data/schema/integrity.test.js
  • ghost/core/test/unit/server/services/machine-payments/adapters.test.js
  • ghost/core/test/unit/server/services/machine-payments/deposit-address-store.test.js
  • ghost/core/test/unit/server/services/machine-payments/service.test.js
🚧 Files skipped from review as they are similar to previous changes (11)
  • ghost/core/core/frontend/helpers/ghost_head.js
  • ghost/core/test/unit/server/data/schema/integrity.test.js
  • ghost/core/core/server/data/schema/schema.js
  • ghost/core/test/unit/server/services/machine-payments/adapters.test.js
  • ghost/core/core/server/services/machine-payments/events/machine-payment-event.js
  • ghost/core/core/server/services/machine-payments/stripe/deposit-address-store.js
  • ghost/core/core/server/services/machine-payments/stripe/payment-recorder.js
  • ghost/core/test/unit/server/services/machine-payments/service.test.js
  • ghost/core/core/server/services/machine-payments/index.js
  • ghost/core/test/unit/server/services/machine-payments/deposit-address-store.test.js
  • ghost/core/core/server/services/machine-payments/pricing.js

louisghost and others added 3 commits August 10, 2026 11:41
ref #29840

- SettingValue now includes number for machine_payments_amount, which broke a narrow type in use-member-views
- Core settings allowlist needed the new secret/deposit-address keys

Co-authored-by: Cursor <cursoragent@cursor.com>
ref #29840

- Codecov e2e-tests only counts server e2e/integration hits, so unpaid markdown/controller branches were dragging project coverage below the 0.2% threshold

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ghost/core/test/e2e-frontend/machine-payments-markdown.test.js`:
- Around line 82-151: Extend the payment e2e coverage around the existing
markdown tests to verify that HTML permalinks and relevant Content API routes
reject a Payment Authorization header for gated posts, rather than invoking
machine-payment orchestration. Add cases for explicit HTML requests and Accept:
text/markdown requests, covering the applicable API endpoints while preserving
the existing gated-content status and response assertions.

In
`@ghost/core/test/integration/services/machine-payments/content-loader.test.js`:
- Around line 57-70: Extend the idempotency test around the existing payload and
repository.save calls to save a second payload with the same reference but
protocol set to x402, then assert its event ID differs from first.id. Preserve
the existing assertion that replaying the identical mpp payload returns the same
ID.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1e6b750e-d117-43cb-b097-7b45a27dd30d

📥 Commits

Reviewing files that changed from the base of the PR and between eabdf7f and 76e93e4.

⛔ Files ignored due to path filters (1)
  • ghost/core/test/e2e-api/admin/__snapshots__/settings.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (4)
  • ghost/core/test/e2e-api/admin/settings.test.js
  • ghost/core/test/e2e-frontend/llms-routes.test.js
  • ghost/core/test/e2e-frontend/machine-payments-markdown.test.js
  • ghost/core/test/integration/services/machine-payments/content-loader.test.js

Comment thread ghost/core/test/e2e-frontend/machine-payments-markdown.test.js
@louisghost

Copy link
Copy Markdown
Contributor Author

Reference PR: #28291

louisghost and others added 9 commits August 10, 2026 13:03
ref #29840

- CodeRabbit: Payment credentials must not unlock HTML, Accept-markdown, or Content API for gated posts
- Composite protocol+reference uniqueness and llms discoverability branches need e2e/integration hits for Codecov

Co-authored-by: Cursor <cursoragent@cursor.com>
ref #29840

- The temporary paid post was left in the DB and shifted later browse snapshots (total 17 → 18)

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

@9larsons 9larsons left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Sent you suggestions separately.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

migration [pull request] Includes migration for review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants