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.