Skip to content

feat: the heartbeat response carries what the worker's platforms earned - #242

Merged
GeiserX merged 3 commits into
mainfrom
feat/heartbeat-returns-earnings
Aug 5, 2026
Merged

feat: the heartbeat response carries what the worker's platforms earned#242
GeiserX merged 3 commits into
mainfrom
feat/heartbeat-returns-earnings

Conversation

@GeiserX

@GeiserX GeiserX commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Server half of CashPilot-android-35t (P1). The client half follows in the Android repo.

The problem

The Android app is called CashPilot and shows no money at all. grep -riE "earning|balance|payout" across its source returns nothing. It makes exactly one HTTP call — POST /api/workers/heartbeat — and never reads anything back.

It can't just call an earnings endpoint. Every one goes through _require_auth_api, which requires a user session; a per-worker key reads none of them. The alternative — giving a phone an owner-level credential so it can render a number — is a bad trade.

So the figures ride back on the one call the worker is already authenticated for. No new credential, no new auth model, and the phone already polls.

The honesty constraint, which shaped the payload

Earnings are collected per platform, from the provider's account. They cannot be attributed to a device: if two machines both run Grass, the provider reports one balance and nothing can split it.

So this never says "this device earned X." It says "the platforms this device is running earned X on your account", and flags every platform running on more than one worker:

"earnings": {
  "window_days": 30,
  "currency": "USD",
  "platforms": [
    {"slug": "grass",     "usd": 12.5, "shared_with_other_workers": true},
    {"slug": "titan",     "usd": null, "shared_with_other_workers": false}
  ],
  "total_usd": 12.5,
  "platforms_without_readings": ["titan"]
}

Three deliberate choices, each with a control proving it:

  • A platform with no reading is null, never 0.0. Absent means nothing was ever collected — usually no collector, or credentials never entered.
  • The total sums only what is known, and is null when nothing is. A total that treats unknown as zero is the same lie in aggregate.
  • The key is omitted entirely when it can't be produced, rather than sent as {} — so a client can tell unknown from nothing earned.

A database failure returns None rather than raising. The heartbeat is how a fleet stays alive; it must never fail over a display nicety.

Evidence

15 tests. Five negative controls, all caught — and they target the honesty properties specifically, because those are the ones a well-meaning future edit would quietly remove:

reverted result
unknown rendered as 0.0 3 failed
total zeroes the unknowns 1 failed
shared-platform flag hardcoded false (implies device attribution) 1 failed
DB failure propagates and breaks the heartbeat 1 failed
empty object instead of omitting the key 1 failed

There is also a test asserting no key in the payload contains "device" — a guard against someone later adding a device_usd field that would be a fabrication by construction.

Source verified byte-identical after every revert.

Gates: ruff clean, all four browser-free harnesses PASS, 3308 passed, 95.44% coverage.

GeiserX added 3 commits August 5, 2026 14:07
MkDocs builds every file in docs_dir whether or not it appears in nav, so three
agent scratch files were live on the public site despite being in no menu:

  https://geiserx.github.io/CashPilot/GOAL/               -> HTTP 200
  https://geiserx.github.io/CashPilot/AUTOPILOT-WORKLOG/  -> HTTP 200

Between them they published this project's internal planning verbatim, quoted
the maintainer directly, and printed a real Mysterium node identity address,
which links this public repository to a specific earning node.

`mkdocs build --strict` does NOT catch this. validation.nav.omitted_files
defaults to `info` and --strict only promotes `warn` to an error, so the orphans
are reported and the build stays green. Exclusion has to be explicit.

Verified by building the site: 64 pages, all three excluded, homepage and
guides intact. Removing the block makes all three reappear.
Excluding them from the built site stopped the publication; it left them
tracked, so the next agent run would commit fresh content straight back into a
public repository.

docs/GOAL.md and docs/AUTOPILOT-WORKLOG.md are now untracked (kept on disk) and
all three are gitignored. The guard covers both directions: excluded from the
site AND absent from the index.

NOTE, and it is not solved by this commit: both files have been tracked since
e631e3a, so their content -- including a real Mysterium node identity address --
is in this public repository's HISTORY. Untracking stops it growing. Removing
what is already there needs a history rewrite, which is the maintainer's call.
The Android client is named CashPilot and shows no money at all -- grep for
earning|balance|payout across its source returns nothing. It makes exactly one
HTTP call, POST /api/workers/heartbeat, and never reads anything back.

It cannot simply call an earnings endpoint: every one of them goes through
_require_auth_api, which needs a user session. A per-worker key cannot read any
of them, and handing a phone an owner-level credential so it can render a number
would be a bad trade. So the figures ride back on the one call the worker is
already authenticated for.

THE HONESTY CONSTRAINT SHAPES THE PAYLOAD. Earnings are collected per PLATFORM
from the provider's account and cannot be attributed to a device: if two
machines run Grass, the provider reports one balance and nothing can split it.
The response therefore never claims a device earned anything. It reports what
the platforms on that device earned, and flags every platform running on more
than one worker so a client can say so too.

Absent stays absent. A platform with no reading is null, never 0.0; the total
sums only what is known and is null when nothing is; and the whole key is
OMITTED rather than sent empty, so a client can tell unknown from nothing.

A database failure returns None rather than raising -- the heartbeat is how a
fleet stays alive and must never fail on a display nicety.
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@GeiserX, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 14 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9a8b4623-793d-4e02-b3b3-6021cc9ed55f

📥 Commits

Reviewing files that changed from the base of the PR and between 325ee40 and 963295d.

📒 Files selected for processing (7)
  • .gitignore
  • app/main.py
  • docs/AUTOPILOT-WORKLOG.md
  • docs/GOAL.md
  • mkdocs.yml
  • tests/test_beads_batch_54.py
  • tests/test_docs_never_publish_agent_files.py

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.

@GeiserX
GeiserX merged commit 4a83fed into main Aug 5, 2026
6 checks passed
@GeiserX
GeiserX deleted the feat/heartbeat-returns-earnings branch August 5, 2026 13:56
@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.42857% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 95.40%. Comparing base (325ee40) to head (963295d).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
app/main.py 96.42% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main     #242   +/-   ##
=======================================
  Coverage   95.39%   95.40%           
=======================================
  Files          47       47           
  Lines        6127     6155   +28     
=======================================
+ Hits         5845     5872   +27     
- Misses        282      283    +1     
Files with missing lines Coverage Δ
app/main.py 97.74% <96.42%> (-0.03%) ⬇️
🚀 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.

GeiserX added a commit that referenced this pull request Aug 5, 2026
…ed (#242)

* fix: stop publishing agent working files on the public docs site

MkDocs builds every file in docs_dir whether or not it appears in nav, so three
agent scratch files were live on the public site despite being in no menu:

  https://geiserx.github.io/CashPilot/GOAL/               -> HTTP 200
  https://geiserx.github.io/CashPilot/AUTOPILOT-WORKLOG/  -> HTTP 200

Between them they published this project's internal planning verbatim, quoted
the maintainer directly, and printed a real Mysterium node identity address,
which links this public repository to a specific earning node.

`mkdocs build --strict` does NOT catch this. validation.nav.omitted_files
defaults to `info` and --strict only promotes `warn` to an error, so the orphans
are reported and the build stays green. Exclusion has to be explicit.

Verified by building the site: 64 pages, all three excluded, homepage and
guides intact. Removing the block makes all three reappear.

* fix: never commit the agent working files either

Excluding them from the built site stopped the publication; it left them
tracked, so the next agent run would commit fresh content straight back into a
public repository.

docs/GOAL.md and docs/AUTOPILOT-WORKLOG.md are now untracked (kept on disk) and
all three are gitignored. The guard covers both directions: excluded from the
site AND absent from the index.

NOTE, and it is not solved by this commit: both files have been tracked since
607b19b, so their content -- including a real Mysterium node identity address --
is in this public repository's HISTORY. Untracking stops it growing. Removing
what is already there needs a history rewrite, which is the maintainer's call.

* feat: the heartbeat response carries what the worker's platforms earned

The Android client is named CashPilot and shows no money at all -- grep for
earning|balance|payout across its source returns nothing. It makes exactly one
HTTP call, POST /api/workers/heartbeat, and never reads anything back.

It cannot simply call an earnings endpoint: every one of them goes through
_require_auth_api, which needs a user session. A per-worker key cannot read any
of them, and handing a phone an owner-level credential so it can render a number
would be a bad trade. So the figures ride back on the one call the worker is
already authenticated for.

THE HONESTY CONSTRAINT SHAPES THE PAYLOAD. Earnings are collected per PLATFORM
from the provider's account and cannot be attributed to a device: if two
machines run Grass, the provider reports one balance and nothing can split it.
The response therefore never claims a device earned anything. It reports what
the platforms on that device earned, and flags every platform running on more
than one worker so a client can say so too.

Absent stays absent. A platform with no reading is null, never 0.0; the total
sums only what is known and is null when nothing is; and the whole key is
OMITTED rather than sent empty, so a client can tell unknown from nothing.

A database failure returns None rather than raising -- the heartbeat is how a
fleet stays alive and must never fail on a display nicety.
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.

1 participant