verstak-docs/docs/superpowers/plans/2026-07-12-alpha-browser-do...

9.7 KiB

Alpha Browser Domain Activity Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox syntax for tracking.

Goal: Add explicitly consented, privacy-minimal active-tab domain timing to the browser extension and deliver immutable activity batches to Desktop.

Architecture: Manual captures remain unchanged. A new tracker stores mutable accumulators, immutable pending batches, and acknowledged IDs locally; Desktop accepts a separate authenticated activity endpoint and emits browser.activity.domain.

Tech Stack: Plain WebExtension JavaScript, Node tests, Go HTTP handler tests, shared JSON vectors.

Global Constraints

  • Passive tracking defaults to disabled and requires explicit informed consent.
  • Track only normalized hostname plus duration for an active tab in a focused window.
  • Never send URL, title, text, page content, keystrokes, or history.
  • A pending batch is byte-for-byte immutable; acknowledge only its own ID.
  • Discard ambiguous or negative clock deltas and gaps over 10 minutes.
  • Use hostname-normalization-v1 for extension, Desktop receiver, exclusions, and bindings.

Task 1: Define and test canonical hostname normalization

Files:

  • Create: /home/mirivlad/git/verstak2/verstak-sdk/schemas/hostname-normalization-v1.json
  • Create: /home/mirivlad/git/verstak2/verstak-browser-extension/shared/hostname.js
  • Create: /home/mirivlad/git/verstak2/verstak-browser-extension/shared/hostname-normalization-v1.json
  • Create: /home/mirivlad/git/verstak2/verstak-browser-extension/scripts/test-hostname.js
  • Create: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/hostname/normalize.go
  • Create: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/hostname/normalize_test.go
  • Create: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/hostname/testdata/hostname-normalization-v1.json

Interfaces:

  • Produces JavaScript normalizeHostnameV1(value, mode).

  • Produces Go hostname.NormalizeV1(value string, mode Mode) (string, error).

  • Mode is URLSource for HTTP(S) URLs and BareHost for binding/exclusion input.

  • Step 1: Write the shared vector corpus and failing tests

Include vectors for Unicode/punycode, trailing dot, port stripping/rejection, IPv4, bracketed IPv6, localhost, one-label internal names, malformed labels, and an overlong name:

[
  {"mode":"bare","input":"пример.рф.","want":"xn--e1afmkfd.xn--p1ai"},
  {"mode":"url","input":"https://Example.COM:8443/a","want":"example.com"},
  {"mode":"bare","input":"example.com:8443","error":"port"},
  {"mode":"bare","input":"[::1]","want":"::1"}
]
  • Step 2: Run red

Run:

cd /home/mirivlad/git/verstak2/verstak-browser-extension
node scripts/test-hostname.js
cd /home/mirivlad/git/verstak2/verstak-desktop
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/hostname -count=1

Expected: both fail because the normalizers do not exist.

  • Step 3: Implement both normalizers

Use the browser URL parser plus ASCII IDNA conversion in JavaScript. Use net/url, net/netip, and golang.org/x/net/idna.Lookup.ToASCII in Go. Store ASCII A-labels, lowercase names, no ports or IPv6 brackets, and reject malformed or over-limit values.

  • Step 4: Run green and commit

Run:

cd /home/mirivlad/git/verstak2/verstak-browser-extension && npm test
cd /home/mirivlad/git/verstak2/verstak-desktop && GOCACHE=/tmp/verstak-go-cache go test ./internal/core/hostname -count=1
cmp /home/mirivlad/git/verstak2/verstak-sdk/schemas/hostname-normalization-v1.json /home/mirivlad/git/verstak2/verstak-browser-extension/shared/hostname-normalization-v1.json
cmp /home/mirivlad/git/verstak2/verstak-sdk/schemas/hostname-normalization-v1.json /home/mirivlad/git/verstak2/verstak-desktop/internal/core/hostname/testdata/hostname-normalization-v1.json
git -C /home/mirivlad/git/verstak2/verstak-sdk add schemas/hostname-normalization-v1.json
git -C /home/mirivlad/git/verstak2/verstak-sdk commit -m "feat: define hostname normalization vectors"
git -C /home/mirivlad/git/verstak2/verstak-browser-extension add shared/hostname.js shared/hostname-normalization-v1.json scripts/test-hostname.js
git -C /home/mirivlad/git/verstak2/verstak-browser-extension commit -m "feat: normalize activity hostnames"
git -C /home/mirivlad/git/verstak2/verstak-desktop add internal/core/hostname
git -C /home/mirivlad/git/verstak2/verstak-desktop commit -m "feat: normalize browser hostnames"

Expected: every copied corpus passes in both implementations.

Task 2: Add a separate authenticated Desktop activity receiver

Files:

  • Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/browserreceiver/receiver.go
  • Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/core/browserreceiver/receiver_test.go
  • Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app.go
  • Modify: /home/mirivlad/git/verstak2/verstak-desktop/internal/api/app_test.go

Interfaces:

  • Accepts POST /activities with BrowserDomainActivity.

  • Emits browser.activity.domain with normalized hostname, duration, ID, and optional workspaceId.

  • Returns status accepted and activityId for first delivery and duplicate delivery.

  • Step 1: Write failing handler tests

Create a valid immutable request and then retry the same ID:

payload := "{\"schemaVersion\":1,\"activityId\":\"a-1\",\"hostname\":\"пример.рф\",\"durationSeconds\":300,\"startedAt\":\"2026-07-12T10:00:00Z\",\"endedAt\":\"2026-07-12T10:05:00Z\"}"
req := httptest.NewRequest(http.MethodPost, "/activities", strings.NewReader(payload))
req.Header.Set("X-Verstak-Receiver-Token", "pair-token")

Assert invalid tokens, URL-like hostnames, zero or oversize duration, and a duplicate ID do not publish a second event.

  • Step 2: Run red

Run:

cd /home/mirivlad/git/verstak2/verstak-desktop
GOCACHE=/tmp/verstak-go-cache go test ./internal/core/browserreceiver -run Activity -count=1

Expected: FAIL because /activities is not routed.

  • Step 3: Implement the bounded idempotent route

Add BrowserDomainActivity, a 30-day bounded receiver ID cache, and handleActivity which invokes hostname.NormalizeV1. Do not reuse capture types or persistence. In App, resolve an active exact hostname binding to workspaceId and a path cache before Activity receives the event.

  • Step 4: Run green and commit

Run:

GOCACHE=/tmp/verstak-go-cache go test ./internal/core/browserreceiver ./internal/api -count=1
git add internal/core/browserreceiver/receiver.go internal/core/browserreceiver/receiver_test.go internal/api/app.go internal/api/app_test.go
git commit -m "feat: receive browser domain activity"

Expected: authentication, normalization, idempotency, and binding tests pass.

Task 3: Build the opt-in extension tracker and immutable delivery queue

Files:

  • Create: /home/mirivlad/git/verstak2/verstak-browser-extension/shared/activity-tracker.js
  • Create: /home/mirivlad/git/verstak2/verstak-browser-extension/scripts/test-activity-tracker.js
  • Modify: /home/mirivlad/git/verstak2/verstak-browser-extension/shared/api.js
  • Modify: /home/mirivlad/git/verstak2/verstak-browser-extension/shared/background.js
  • Modify: /home/mirivlad/git/verstak2/verstak-browser-extension/shared/popup/popup.html
  • Modify: /home/mirivlad/git/verstak2/verstak-browser-extension/shared/popup/popup.js
  • Modify: /home/mirivlad/git/verstak2/verstak-browser-extension/shared/popup/popup.css
  • Modify: /home/mirivlad/git/verstak2/verstak-browser-extension/shared/locales/en.json
  • Modify: /home/mirivlad/git/verstak2/verstak-browser-extension/shared/locales/ru.json
  • Modify: /home/mirivlad/git/verstak2/verstak-browser-extension/chromium/manifest.json
  • Modify: /home/mirivlad/git/verstak2/verstak-browser-extension/firefox/manifest.json

Interfaces:

  • Persists activeAccumulator, pendingBatches, and acknowledgedIds under verstak.activityTracker.

  • Produces sendActivity(receiverUrl, token, immutablePayload).

  • Settings include passiveActivityEnabled false and excludedDomains empty.

  • Step 1: Write failing state-machine tests

Cover consent, active-tab-only timing, long-gap discard, and the A/B batch case:

tracker.addElapsed('example.com', 600, start, end);
const batchA = tracker.freeze('example.com');
tracker.addElapsed('example.com', 300, laterStart, laterEnd);
assert.equal(tracker.pendingBatches[0].payload.durationSeconds, 600);
tracker.acknowledge(batchA.activityId);
assert.equal(tracker.activeAccumulator['example.com'].durationSeconds, 300);
  • Step 2: Run red

Run:

cd /home/mirivlad/git/verstak2/verstak-browser-extension
node scripts/test-activity-tracker.js

Expected: FAIL because the state machine is absent.

  • Step 3: Implement tracker, disclosure, and listeners

Implement the tracker as pure functions. In background.js, register tabs.onActivated, tabs.onUpdated, windows.onFocusChanged, idle.onStateChanged, and a five-minute alarm only while opted in. Set idle detection to 600 seconds. On a gap over 600 seconds, clock rollback, lock, or browser startup, reset the active checkpoint without adding time. Retry frozen batches oldest-first and remove only matching IDs.

Show an unchecked Russian/English consent explanation in settings. Add windows, alarms, and idle permissions to both manifests. Keep manual capture queue and popup actions unchanged.

  • Step 4: Run green and commit

Run:

npm test
node scripts/test-activity-tracker.js
git add shared chromium/manifest.json firefox/manifest.json scripts/test-activity-tracker.js
git commit -m "feat: add opt-in browser domain tracker"

Expected: tracker unit tests and existing extension tests pass.