diff --git a/AGENTS.md b/AGENTS.md index c55a1d28..fd82cefa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -190,6 +190,15 @@ When moving a feature to a controller, follow these steps: --- +## 11. Telemetry & Error Aggregation + +* **Tooling:** Use `npm run telemetry:aggregate` (which runs `scripts/agent/telemetry-aggregator.mjs`) to collect, sanitize, and aggregate errors from CI/test runs, smoke tests, and agent logs. +* **Privacy Policy:** The tool automatically strips PII (IPs, emails, hex keys, bech32 keys) from logs. **Do not** manually inspect raw logs containing PII unless necessary for debugging in a secure environment; rely on the sanitized reports in `ai/reports/`. +* **Opt-in:** Telemetry generation is opt-in. You must set the environment variable `ENABLE_TELEMETRY=true` when running the aggregator. +* **Reports:** Aggregated reports are generated in `ai/reports/telemetry-YYYYMMDD.md` and `artifacts/error-aggregates.json`. These files provide prioritized issues for triage. + +--- + ## Next Please read these documents next. diff --git a/artifacts/error-aggregates.json b/artifacts/error-aggregates.json index 5b83adf7..2b9699de 100644 --- a/artifacts/error-aggregates.json +++ b/artifacts/error-aggregates.json @@ -1,74 +1,13 @@ [ { - "fingerprint": "56489eaa", - "title": "handleModerationBlock requests a block, clears overrides, and refreshes hidden state", - "count": 6, - "sources": [ - "unit-test:test_unit_debug_2.log", - "unit-test:test_unit_debug_3.log", - "unit-test:test_unit_pre_commit.log", - "unit-test:test_unit_retry.log", - "unit-test:test_unit_retry_3.log", - "unit-test:test_unit_retry_4.log" - ], - "severity": "High", - "stack": "TestContext. (file://$REPO/tests$REPO/moderation-overrides.test.mjs:228:10)\n async Test.run (node:internal/test_runner/test:1054:7)\n async Test.processPendingSubtests (node:internal/test_runner/test:744:7)\n ...\n# Subtest: handleModerationBlock returns false when viewer is logged out", - "owner": "QA Team" - }, - { - "fingerprint": "bcbb97f7", - "title": "handleModerationOverride resumes deferred playback", - "count": 2, - "sources": [ - "unit-test:test_unit_retry_2.log", - "unit-test:test_unit_retry_3.log" - ], - "severity": "High", - "stack": "TestContext. (file://$REPO/tests$REPO/moderation-overrides.test.mjs:104:10)\n async Test.run (node:internal/test_runner/test:1054:7)\n async Test.processPendingSubtests (node:internal/test_runner/test:744:7)\n ...\n# Subtest: handleModerationBlock requests a block, clears overrides, and refreshes hidden state", - "owner": "QA Team" - }, - { - "fingerprint": "80a22b69", - "title": "handleModerationBlock requests a block, clears overrides, and refreshes hidden state", + "fingerprint": "e1da49a7", + "title": "load defers permission-required decrypts until explicitly enabled", "count": 1, "sources": [ - "unit-test:test_unit_debug_4.log" + "unit-test:test_unit.log" ], "severity": "High", - "stack": "TestContext. (file://$REPO/tests$REPO/moderation-overrides.test.mjs:229:10)\n async Test.run (node:internal/test_runner/test:1054:7)\n async Test.processPendingSubtests (node:internal/test_runner/test:744:7)\n ...\n# Subtest: handleModerationBlock returns false when viewer is logged out", - "owner": "QA Team" - }, - { - "fingerprint": "5e159618", - "title": "handleModerationBlock requests a block, clears overrides, and refreshes hidden state", - "count": 1, - "sources": [ - "unit-test:test_unit_final.log" - ], - "severity": "High", - "stack": "TestContext. (file://$REPO/tests$REPO/moderation-overrides.test.mjs:232:10)\n async Test.run (node:internal/test_runner/test:1054:7)\n async Test.processPendingSubtests (node:internal/test_runner/test:744:7)\n ...\n# Subtest: handleModerationBlock returns false when viewer is logged out", - "owner": "QA Team" - }, - { - "fingerprint": "4a2440b4", - "title": "handleModerationOverride decorates stored and current videos then refreshes UI", - "count": 1, - "sources": [ - "unit-test:test_unit_retry_2.log" - ], - "severity": "High", - "stack": "TestContext. (file://$REPO/tests$REPO/moderation-overrides.test.mjs:67:10)\n async Test.run (node:internal/test_runner/test:1054:7)\n async startSubtestAfterBootstrap (node:internal/test_runner/harness:296:3)\n ...\n# Subtest: handleModerationOverride resumes deferred playback", - "owner": "QA Team" - }, - { - "fingerprint": "a879eb91", - "title": "handleModerationBlock requests a block, clears overrides, and refreshes hidden state", - "count": 1, - "sources": [ - "unit-test:test_unit_retry_2.log" - ], - "severity": "High", - "stack": "TestContext. (file://$REPO/tests$REPO/moderation-overrides.test.mjs:202:10)\n async Test.run (node:internal/test_runner/test:1054:7)\n async Test.processPendingSubtests (node:internal/test_runner/test:744:7)\n ...\n# Subtest: handleModerationBlock returns false when viewer is logged out", + "stack": "TestContext. (file://$REPO/tests/hashtag-preferences.test.mjs:300:14)\n async Test.run (node:internal/test_runner/test:1054:7)\n async Test.processPendingSubtests (node:internal/test_runner/test:744:7)\n ...\n# Subtest: interest and disinterest lists remain exclusive", "owner": "QA Team" } ] \ No newline at end of file diff --git a/tests/hashtag-preferences.test.mjs b/tests/hashtag-preferences.test.mjs index 612d844a..d79d94a2 100644 --- a/tests/hashtag-preferences.test.mjs +++ b/tests/hashtag-preferences.test.mjs @@ -71,6 +71,10 @@ test.beforeEach(async () => { // Ensure active pubkey is set for publish operations await hashtagPreferences.load("0".repeat(64)); + + if (nostrClient.extensionPermissionCache) { + nostrClient.extensionPermissionCache.clear(); + } }); test.after(() => { @@ -258,11 +262,13 @@ test( nostrClient.writeRelays = relayUrls; const decryptCalls = []; + const permissionState = { enabled: false }; window.nostr = { - enable: async () => true, + enable: async () => { permissionState.enabled = true; }, getPublicKey: async () => pubkey, nip04: { decrypt: async () => { + if (!permissionState.enabled) throw new Error("permission denied"); decryptCalls.push("nip04"); return JSON.stringify({ version: 1, diff --git a/tests/nostr-login-permissions.test.mjs b/tests/nostr-login-permissions.test.mjs index 3ed00d9e..ceebf4ce 100644 --- a/tests/nostr-login-permissions.test.mjs +++ b/tests/nostr-login-permissions.test.mjs @@ -153,8 +153,46 @@ function setupLoginEnvironment({ enableImpl, getPublicKey = HEX_PUBKEY } = {}) { }; } -describe("Nostr Login Permissions", () => { - beforeEach(() => { +test("NIP-07 login requests decrypt permissions upfront", async () => { + const env = setupLoginEnvironment(); + try { + const result = await nip07Provider.login({ nostrClient }); + const pubkey = result.pubkey; + assert.equal(pubkey, HEX_PUBKEY); + assert.ok(env.enableCalls.length >= 1, "extension.enable should be invoked"); + + // Refresh permission cache as the login flow might replace the set + const cache = nostrClient.extensionPermissionCache; + + assert.ok( + cache.has("nip04.encrypt"), + "nip04.encrypt permission should be tracked as granted", + ); + assert.ok( + cache.has("nip04.decrypt"), + "nip04.decrypt permission should be tracked as granted", + ); + assert.ok( + cache.has("nip44.encrypt"), + "nip44.encrypt permission should be tracked as granted", + ); + assert.ok( + cache.has("nip44.decrypt"), + "nip44.decrypt permission should be tracked as granted", + ); + for (const method of EXPECTED_ENCRYPTION_PERMISSIONS) { + assert.ok( + cache.has(method), + `${method} permission should be tracked as granted`, + ); + } + assert.ok( + cache.has("sign_event"), + "sign_event permission should be tracked as granted", + ); + } finally { + env.restore(); + nostrClient.logout(); clearStoredPermissions(); }); @@ -162,10 +200,19 @@ describe("Nostr Login Permissions", () => { clearStoredPermissions(); }); - after(() => { - // Force exit to prevent open handles (e.g. intervals in nostrClient) from hanging the test runner - process.exit(0); - }); + const result = await nip07Provider.login({ nostrClient }); + const pubkey = result.pubkey; + assert.equal(pubkey, HEX_PUBKEY); + + // Refresh cache reference + const cache = nostrClient.extensionPermissionCache; + + for (const method of EXPECTED_ENCRYPTION_PERMISSIONS) { + assert.ok( + cache.has(method), + `nostrClient should track ${method} after login`, + ); + } it("NIP-07 login requests decrypt permissions upfront", async () => { const env = setupLoginEnvironment();