Skip to content

A Demo may count its visits with Umami, and nothing else may (Closes #408) - #427

Merged
BattermanZ merged 1 commit into
devfrom
feature/408-demo-umami
Oct 23, 2026
Merged

BattermanZ merged 1 commit into
devfrom
feature/408-demo-umami

Conversation

@BattermanZ

Copy link
Copy Markdown
Member

Summary

A Demo given an Umami counts its visits with it. Any other Kamosu never reads the two settings.

 Config::from_lookup
   demo = KAMOSU_DEMO_ERASES_LIBRARY_DAILY == "true"
+  analytics = if demo { Analytics::from_settings(KAMOSU_DEMO_UMAMI_SCRIPT, KAMOSU_DEMO_UMAMI_WEBSITE_ID) } else { none }
+    # both or neither; http(s), a plain name or IPv4 host, no `*`, no `;`, no IPv6

 browser_policy::stamp            # every answer
-  script-src 'self' <hashes>
+  script-src 'self' <hashes> [umami origin]
+  connect-src 'self' umami origin          # only in a counting Demo

 instance_status
+  analytics: { script, website_id }        # only in a counting Demo

 interface::serve("service-worker.js")
+  + "// This Demo counts its visits with the Umami at <origin>"   # only in a counting Demo

 Shell → learnWhetherADemo
+  countVisits(status.analytics)            # one <script defer data-website-id>, once
 reads.ts
+  instance_status: 'server-first'

Why the worker line: a phone keeps the shell with the headers it came with, so a policy that changes on the same version stayed stale until the next upgrade. A changed worker makes the browser install it again, and the install fetches / again under the current policy. server-first stops a kept instance_status from loading the script after counting stops.

Evidence

  • Before: a Demo had no way to count its visits, and the policy refused every other origin.
    After: just check and just test pass: 1,079 Rust tests, 1,379 screen tests and 68 tool tests. New tests:
    • the_policy_without_analytics_is_what_it_always_was: the whole policy string as it was before.
    • an_instance_that_is_no_demo_counts_nothing_whatever_is_set
    • a_setting_kamosu_cannot_use_counts_nothing_and_says_why: covers *, *.example.com, a.example;sandbox and [::1].
    • a_demo_lets_its_umami_through_and_nothing_else: run through the real door. It checks the policy on the shell, a screen and the stylesheet, instance_status, and the worker's bytes against an ordinary instance's.
    • a vitest check that the script is loaded once, and not at all without an Umami.
  • Live, in Chrome against a throwaway Demo and a stand-in Umami serving the real tracker script:
    • The Demo's policy names only the stand-in.
    • The arrival and a move to a recipe each sent one count, with no cookie.
    • From the same page, another origin was refused for both a script (script-src-elem) and a request (connect-src).
    • An ordinary instance with both settings set sent nothing, loaded no script, and kept the old policy.
    • Stale copy, before the fix: with one kept browser profile, adding Umami on the same version left the script blocked on every later visit.
    • Stale copy, after the fix: one stale visit, then the new policy and counts. After counting stopped, one visit had the old policy but loaded no script and sent nothing, then the plain policy.

Merge Danger

Door: two-way

Unset, nothing changes. The two settings are new and read only in demo mode.

Blast Radius: small

  • instance_status is now read server-first everywhere. Sign-in, Settings and the reconnect probe wait for the server when online, and use the phone's copy offline as before.
  • Known gap, left open: a returning visitor can still send one count to the Operator's own Umami. This needs a Demo that stopped counting on the same version, a visit while Kamosu is down, and Umami still up.

Closes #408

🤖 Generated with Claude Code

https://claude.ai/code/session_01126KLKw9pGwH2vG3r1i2VL

Two optional settings, KAMOSU_DEMO_UMAMI_SCRIPT and
KAMOSU_DEMO_UMAMI_WEBSITE_ID, read only in demo mode. A Demo given both
opens script-src and connect-src to the script's one origin, names the
script in instance_status, and the interface loads it once. An instance
that is no Demo never reads them, and its policy is unchanged byte for
byte.

The address must be an http(s) name or IPv4 address, so a `*` or a `;`
cannot widen the policy. A counting Demo adds one line naming its Umami
to the service worker it serves, so a phone that kept the shell under
another policy installs the worker again and keeps it under the current
one. instance_status is read server-first, so a kept answer cannot load
the script after counting stops.

Closes #408

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01126KLKw9pGwH2vG3r1i2VL
@BattermanZ
BattermanZ merged commit 254fb1e into dev Oct 23, 2026
1 check failed
@BattermanZ
BattermanZ deleted the feature/408-demo-umami branch October 23, 2026 05:40
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