Skip to content

feat(stats): show how FTW Cloud runs in the private view - #105

Merged
frahlg merged 8 commits into
mainfrom
feat/internal-ops-view
Oct 24, 2026
Merged

frahlg merged 8 commits into
mainfrom
feat/internal-ops-view

Conversation

@frahlg

@frahlg frahlg commented Oct 24, 2026 •

Copy link
Copy Markdown
Member

Problem

Fredrik, 24 October 2026: Sourceful needs internal statistics to run FTW Cloud and understand the fleet, for internal use, in one place, without building a separate system. The private view of stats.ftw.energy is that place: it already sits behind Cloudflare Access and checks the JWT itself.

What changes

  • Collector (stats/src/ops.ts, minute 23 each hour): reads https://cloud.ftw.energy/v1/ops with the new CLOUD_OPS_KEY secret, from an hour before the newest hour kept, so a restart's late part of an hour replaces the row. No key: the run records awaiting key. Cloud without a key (404): awaiting key on Cloud. A refusal, a redirect, an answer over 1 MiB or one bad field: the run fails and nothing is stored.
  • Strict parser (parseOps), in the style of stats/src/fleet.ts: exact field sets at every level (feed, hour, gauge, latency, fleet day, energy day), series names ^[a-z0-9_]{1,80}$, whole non-negative counts, gauges with min ≤ mean ≤ max, quantiles in order and only with a count, settled hours only, days only once closed (a fleet day after it ends, an energy date four days after), at most a week of hours. A test runs it against a feed ftwcloudd wrote (stats/test/fixtures/cloud-ops.json).
  • D1 (0005_cloud_ops.sql): cloud_ops_hours (90 days) and cloud_ops_days (the latest exact view only). Both pruned hourly.
  • Private view, new panel Service operations: boxes and phones connected, the share on QUIC, data written this month against AWS's 100 GB of free data out, the last 48 hours, drops by reason, push results, assistant calls by outcome, the slowest p99 of the day, each deploy's drain and the box handshakes that followed, exact daily reports with their labels, and exact energy dates. Reviewed in a browser at desktop and 375 px, no console errors.
  • The public view does not change; a test checks it carries none of this.
  • Docs: stats/README.md ("FTW Cloud operations"), the secret, retention.

Why none of it names a home

Cloud sends sums over the whole service and totals of reports that carry no id, and only closed days, so two pulls never differ by one report. The parser refuses any field the schema does not name, and a test checks the D1 columns. The limit, plainly: while the fleet is a handful of homes, a small count can still describe one of a few people staff know. srcfl/ftw-cloud privacy/internal-operations.md has the full account.

Health counts (second commit)

srcfl/ftw#1821 adds four coarse health buckets to the anonymous fleet report (ftw.fleet/2), and srcfl/ftw-cloud#52 carries their exact counts in /v1/ops fleet days. The parser takes a day with all four health maps, each label from its fixed list and no count above the day's reports, or a day with none, and refuses anything between. The private view shows them as chips beside the other labels (reviewed in a browser). The public view does not show them. The fixture now holds an ftw.fleet/2 report as ftwcloudd writes it.

Review fixes (third commit)

  • A quantile past ftwcloudd's last bucket (null beside a count) reads as over_10s, shown "over 10 s", the slowest case; it was dropped or read as 0.
  • The slowest assistant call keeps to the 7 days the panel names.
  • The public page no longer carries the panel's markup, copy or script; a test reads the public HTML.
  • A week without hours says so rather than "Nothing dropped"; daily totals older than two days read stale; the last 48 hours are 48 slots, a missing hour a gap with its own tooltip; the shards of an hour add together; data out names how many of the month's hours it covers.

Pairs and order

Nothing is deployed. Turning it on: srcfl/ftw-cloud deploy/README.md, "Watching it".

Checks

npm run test:stats: 44 passed. npm run check:stats and npm run build:stats pass. npm run verify: four tests in src/views/Energy.svelte.test.ts failed under the full parallel run and pass alone (10/10); this PR does not touch src/.

🤖 Generated with Claude Code

The "Energy via FTW Cloud" section shows the newest public day of the
anonymous daily energy reports: its report count and its solar, grid,
battery and vehicle sums, and the public days of the last 30. Labelled as
reports, not homes.

The Worker reads https://cloud.ftw.energy/v1/energy/public hourly with the
other Cloud feeds, parses it as strictly as the fleet feed (exact fields,
closed dates only, at least five reports or all null, sums in range) and
keeps only the latest copy in D1 (migration 0004). A Cloud that does not
serve the route yet is recorded as awaiting source, not as a failure.

Pairs with srcfl/ftw and srcfl/ftw-cloud feat/anonymous-energy-ohttp.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-24T06:22:37.513353Z c30e3b8 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
ftw-webapp 51c4d64 Commit Preview URL

Branch Preview URL
Oct 24 2026, 09:42 AM

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c30e3b8d43

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread stats/src/ops.ts Outdated
Comment on lines +251 to +253
function maxOf(rows: { latency: Record<string, Latency> }[], key: string): number | null {
const v = rows.map(r => r.latency[key]?.p99).filter((p): p is number => typeof p === 'number')
return v.length ? Math.max(...v) : null

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve overflowing quantiles when computing the slowest p99

When an hour has samples but its p99 exceeds the feed's 10-second histogram limit, its p99 is null (which the dashboard's ms() formatter displays as “over 10 s”). Filtering out every null here makes a faster hour win instead: an hour with n: 1, p99: null followed by an hour with p99: 5 produces a daily slowest p99 of 5 ms. This hides the worst latency precisely during slowdowns. Distinguish absent or zero-sample series from overflowing quantiles and propagate the latter through the maximum; also ensure the assistant-call renderer does not suppress that result.

Useful? React with 👍 / 👎.

Comment thread stats/src/ops.ts Outdated
alert: pushResults(totals, 'apns_alert_'), widgets: pushResults(totals, 'apns_widgets_'), browser: pushResults(totals, 'webpush_'),
breaker_opened: totals['apns_breaker_open'] ?? 0, coalesced: totals['push_coalesced'] ?? 0,
},
mcp: { outcomes: prefixed(totals, 'mcp_'), p99_ms_max: maxOf(rows, 'mcp_call_seconds') },

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restrict assistant-call latency to the advertised seven-day window

Once more than seven days of hours have been collected, this maximum includes the entire query window (at least 30 days), whereas the adjacent outcome counts include only the last seven days. For example, a 9-second p99 from ten days ago still appears under “Assistant calls, 7 days” even if every call this week completed in 5 ms. Filter the latency rows to the same seven-day window before calling maxOf so old incidents age out of the weekly panel.

Useful? React with 👍 / 👎.

…hanged

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg force-pushed the feat/internal-ops-view branch from 3b81c2b to 9e09a86 Compare October 24, 2026 06:46
frahlg added a commit that referenced this pull request Oct 24, 2026
Review of #105:
- A quantile past ftwcloudd's last bucket comes as null beside a count.
  It was dropped or read as 0; it now reads as "over 10 s" (over_10s in
  the API), the slowest case, in every list and in forward_p99_ms.
- The slowest assistant call keeps to the 7 days the panel names.
- The public page no longer carries the panel's markup, copy or script.
  A test reads the public HTML.

Also: a week without hours says so instead of "Nothing dropped"; old
daily totals read stale; missing hours are gaps in the chart, with their
own tooltip; the shards of an hour are added together; data out says
how many of the month's hours it covers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
frahlg and others added 5 commits October 24, 2026 09:04
… is stale

From review of #104:
- A feed stamped up to 2 h ahead of the Worker could pass a day as closed
  one day early. The closed-day bound now uses the earlier of the feed's
  stamp and the Worker's clock.
- observed_at and each date must be strings; an array no longer passes
  through String().
- The daily energy feed has its own freshness entry ("Daily energy", stale
  after 48 hours, since Cloud adds a day once a day); the hourly usage dot
  is now "Hourly energy". A stale feed says so in the note and on the tiles,
  also when every day is withheld.
- Cloud now withholds a sum that one to four of a day's reports had any of
  (srcfl/ftw-cloud#50). The parser takes null for such a sum, and the 30-day
  window sums each field over the days that show it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Worker reads FTW Cloud's internal feed (GET /v1/ops, with the
CLOUD_OPS_KEY secret) once an hour at minute 23 and keeps it in D1:
the service's hourly sums for 90 days, and the latest exact totals of
closed fleet and energy report days. The parser takes only the fields
ftw.cloud-ops/1 names, at every level, and refuses the whole answer
otherwise; a test holds it to a feed ftwcloudd wrote.

The private view shows boxes and phones connected, the share on QUIC,
data written this month against AWS's 100 GB of free data out, drops
by reason, push results, assistant calls by outcome, the slowest p99 of
the day, each deploy's drain, and the exact daily reports. Nothing in it
names a home, a device or an address. The public view is unchanged.

Stacked on #104, whose energy fields it shares.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FTW Cloud's /v1/ops carries the health counts of ftw.fleet/2 reports in
its closed fleet days (srcfl/ftw-cloud#52, srcfl/ftw#1821): the slowest
plan and fallback plans of each box's last day, the share of failed
driver reads, and the newest update of its last week. The parser takes a
day with all four, each from its fixed list and none above the day's
reports, or a day with none, and refuses anything between. The private
view shows them beside the other labels. The fixture is ftwcloudd's own
answer with an ftw.fleet/2 report in it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #105:
- A quantile past ftwcloudd's last bucket comes as null beside a count.
  It was dropped or read as 0; it now reads as "over 10 s" (over_10s in
  the API), the slowest case, in every list and in forward_p99_ms.
- The slowest assistant call keeps to the 7 days the panel names.
- The public page no longer carries the panel's markup, copy or script.
  A test reads the public HTML.

Also: a week without hours says so instead of "Nothing dropped"; old
daily totals read stale; missing hours are gaps in the chart, with their
own tooltip; the shards of an hour are added together; data out says
how many of the month's hours it covers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The public energy feed may now show null for a sum few reports had
(#104). The internal feed is exact and never does; its parser refuses a
null sum.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg force-pushed the feat/internal-ops-view branch from 415b51f to 35c5cc9 Compare October 24, 2026 07:06
@frahlg
frahlg changed the base branch from feat/anonymous-energy-stats to main October 24, 2026 09:42
# Conflicts:
#	stats/src/index.ts
#	stats/src/store.ts
#	stats/test/worker.test.ts
@frahlg
frahlg merged commit 1a581b7 into main Oct 24, 2026
3 checks passed
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