feat(miner-manage): add optional anonymized Orb telemetry export - #4479
Conversation
The self-host Orb collector (src/selfhost/orb-collector.ts, JSONbored#1255) is ALWAYS-ON for a maintainer's own instance; a miner runs on a third-party contributor's laptop with a much lower consent bar, so this export is OPT-IN (default OFF) — hence "optional". It mirrors the collector's privacy posture but adapts it for the miner: - Repo/PR identifiers are HMAC-anonymized with a per-instance DEDICATED secret (256-bit, generated once via node:crypto, persisted in local SQLite, single- purpose) — the same key-separation posture as getOrCreateAnonSecret, so a repo/PR always hashes the same way but the raw name never leaves the machine. - Only the decision + a low-cardinality reason bucket (already one of the miner's REJECTION_REASONS, else "none") + closedAt are exported — never raw repo names or free text. - The data source is the local pr_outcome ledger (pr-outcome.js readPrOutcomes), not a hosted D1. Local SQLite also holds the per-instance secret and the export cursor (the miner has no system_flags / orb_export_cursor table). Scoped to building the anonymized batch + managing the local secret/cursor; performing the network POST is the caller's job, so the module stays pure over its inputs + local store and needs no network to test. - packages/gittensory-miner/lib/orb-export.js (+ hand-written .d.ts): the store (openOrbExportStore), hmacAnonymize, buildAnonymizedOrbBatch (pure), and the opt-in-gated collectOrbExportBatch. - test/unit/miner-orb-export.test.ts: opt-in gate, secret stability across reopens, anonymization (deterministic + hides raw), reason bucketing, and the malformed-record skip, over a real temp-file SQLite store. Closes JSONbored#4277
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4479 +/- ##
=======================================
Coverage 94.03% 94.03%
=======================================
Files 420 420
Lines 37521 37521
Branches 13701 13701
=======================================
Hits 35282 35282
Misses 1583 1583
Partials 656 656 🚀 New features to boost your workflow:
|
|
Tip 🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩 ✅ Gittensory review result - approve/merge recommendedReview updated: 2026-07-09 21:38:57 UTC
✅ Suggested Action - Approve/Merge
Review summary Nits — 5 non-blocking
Linked issue satisfactionPartially addressed Review context
Contributor next steps
Signal definitions
🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by Gittensory, a quiet PR intelligence layer for OSS maintainers.
|
Closes #4277
What
The self-host Orb collector (
src/selfhost/orb-collector.ts, #1255) is always-on for a maintainer's own instance. A miner runs on a third-party contributor's laptop with a much lower consent bar, so this export is opt-in (default OFF) — the "optional" in the title. It mirrors the collector's privacy posture, adapted for the miner's local SQLite world.Privacy posture (mirrors the collector)
node:crypto, persisted in local SQLite, single-purpose (never any App/webhook credential). A repo/PR always hashes the same way (so the receiver can dedup), but the raw name never leaves the machine.decision+ areasonBucket(already one of the miner'sREJECTION_REASONS, else"none") +closedAtare exported — never raw repo names or free text.pr_outcomeledger (pr-outcome.jsreadPrOutcomes), not a hosted D1. Local SQLite also holds the per-instance secret and the export cursor (the miner has nosystem_flags/orb_export_cursortable).Scoped to building the anonymized batch + managing the local secret/cursor — performing the network POST is the caller's job, so the module stays pure over its inputs + local store and needs no network to test.
API (
packages/gittensory-miner/lib/orb-export.js)ORB_EXPORT_ENABLED_BY_DEFAULT = false— the opt-in default.openOrbExportStore(dbPath?)— local SQLite store:getOrCreateAnonSecret(),getCursor()/setCursor()(0o700dir,0o600file — the secret stays on-machine).hmacAnonymize(value, key)— sha256 HMAC, first 24 hex (mirrors the collector'shmacField).buildAnonymizedOrbBatch(outcomes, key)— pure; maps areadPrOutcomesmap to anonymized rows, deterministic order.collectOrbExportBatch({ store, eventLedger, enabled })— opt-in-gated; returnsnullunless enabled.Files
packages/gittensory-miner/lib/orb-export.js(+ hand-written.d.tssidecar)test/unit/miner-orb-export.test.tsTesting
10/10 tests pass (opt-in gate, secret stability across reopens, anonymization determinism + raw-value hiding, reason bucketing, malformed-record skip — over a real temp-file SQLite store); typecheck clean.
packages/gittensory-miner/lib/**carries no coverage wall.