Skip to content

feat(selfhost): add Gittensory Orb outcome signal collector (#1219) - #1224

Merged
JSONbored merged 5 commits into
mainfrom
feat/gittensory-orb
Jun 24, 2026
Merged

feat(selfhost): add Gittensory Orb outcome signal collector (#1219)#1224
JSONbored merged 5 commits into
mainfrom
feat/gittensory-orb

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Adds Gittensory Orb — a separate, lightweight GitHub App (read-only: pull_requests:read + metadata:read) that records gate verdict + PR outcome locally for self-hosted calibration and opt-in aggregate telemetry. Completely off by default (ORB_ENABLED=false). Nothing leaves the instance without explicit opt-in.

Architecture (inspired by entrius/das-github-mirror):

  • Separate GitHub App for clean opt-in control — operators install Orb independently, can revoke without affecting the main review App
  • One-click setup wizard at GET /orb/setup via GitHub App manifest flow
  • HMAC-SHA256 signed webhook endpoint at POST /orb/webhook
  • Idempotent event recording (UNIQUE (repo, pr_number, head_sha)INSERT OR IGNORE)
  • Hourly signed batch export with HMAC-anonymized repo names (ORB_ANONYMIZE=true by default)
  • ORB_AIR_GAP=true keeps all data strictly local

New files:

  • src/selfhost/orb-setup.ts — manifest builder, setup page, credential exchange, env serializer
  • src/selfhost/orb-webhook.ts — signature verify, gate verdict lookup, PR close + installation handlers
  • src/selfhost/orb-collector.ts — local DB store + hourly batch export with signing + HMAC anonymization
  • migrations/0056_orb_events.sql + migrations/0057_orb_installations.sql
  • 56 tests across all three modules

Modified files:

  • src/server.ts — Orb imports, /orb/setup wizard, /orb/webhook endpoint, pre-init counters, hourly export cron
  • grafana/dashboards/gittensory.json — Orb row (recorded/exported/errors/installs + rate timeseries, dashboard v3)
  • .env.example — Orb env block with all config variables

Depends on: #1157 (Docker/Redis/Qdrant — must merge first)

Scope

  • src/ changes
  • test/ changes (56 new unit tests)
  • migrations/ changes (0056, 0057)
  • No apps/gittensory-ui/ changes
  • No wrangler.jsonc / cf-typegen changes

Validation

  • npx vitest run test/unit/selfhost-orb-collector.test.ts test/unit/selfhost-orb-setup.test.ts test/unit/selfhost-orb-webhook.test.ts — 56/56 passed
  • npx tsc --noEmit — clean (no errors in Orb modules)
  • Full npm run test:ci pending CI

Safety

  • No secrets, tokens, wallets, hotkeys, trust scores, or reward values anywhere in code, comments, tests, or PR text
  • No AI/agent attribution in commits or PR text
  • All outbound data is opt-in (ORB_ENABLED=false by default), HMAC-signed, and anonymizable
  • CSRF nonce on setup wizard (HttpOnly cookie + URL param comparison)
  • Signature verify is timing-safe (timingSafeEqual)
  • Air-gap mode (ORB_AIR_GAP=true) keeps everything strictly local

@dosubot dosubot Bot added the size:L label Jun 24, 2026

@superagent-security superagent-security Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superagent found 1 security concern(s).

Comment thread src/selfhost/orb-collector.ts
@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.94%. Comparing base (0325fb1) to head (a9fca4f).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1224      +/-   ##
==========================================
+ Coverage   94.92%   94.94%   +0.02%     
==========================================
  Files         173      176       +3     
  Lines       19616    19708      +92     
  Branches     7050     7085      +35     
==========================================
+ Hits        18620    18712      +92     
  Misses        396      396              
  Partials      600      600              
Files with missing lines Coverage Δ
src/selfhost/orb-collector.ts 100.00% <100.00%> (ø)
src/selfhost/orb-setup.ts 100.00% <100.00%> (ø)
src/selfhost/orb-webhook.ts 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@superagent-security superagent-security Bot added the pr:flagged PR flagged for review by security analysis. label Jun 24, 2026
@JSONbored
JSONbored force-pushed the feat/gittensory-orb branch from da41ff3 to 3219c7f Compare June 24, 2026 11:00

@superagent-security superagent-security Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Superagent found 1 security concern(s).

Comment thread src/selfhost/orb-collector.ts Outdated
Base automatically changed from feat/self-host-docker to main June 24, 2026 11:09
@dosubot dosubot Bot added size:XXL and removed size:L labels Jun 24, 2026
Gittensory Orb is a separate, lightweight GitHub App (pull_requests:read +
metadata:read) that records gate verdict + PR outcome (merged/closed) locally
for calibration and optional aggregate telemetry. Nothing is sent without
explicit ORB_ENABLED=true.

- Setup wizard at GET /orb/setup — one-click GitHub App manifest flow creates
  the Orb App and writes credentials to /data/gittensory-orb.env
- /orb/webhook handler — HMAC-SHA256 verified; handles pull_request.closed
  (records outcome + gate verdict from review_targets) and installation events
  (tracks which repos have the Orb App installed)
- orb-collector.ts — local DB store (orb_events) + hourly export batch with
  HMAC-signed payload; ORB_ANONYMIZE=true (default) HMAC-hashes repo names;
  ORB_AIR_GAP=true keeps all data local
- Migrations 0056 (orb_events) + 0057 (orb_installations) with UNIQUE
  constraints for idempotent event recording
- Hourly export cron wired into server.ts; also flushes on startup
- Grafana Orb row: recorded/exported/error/installs stats + event rate + pending
  vs exported timeseries (dashboard v3)
- 56 tests across orb-collector, orb-setup, orb-webhook modules
…w selfhost modules

- Fix TS2352 in selfhost-qdrant-vectorize.test.ts: all mock.calls[0] casts now
  use `as unknown as [...]` to satisfy the strict mock type
- orb-collector: cover both sides of `GITHUB_WEBHOOK_SECRET ?? ""` (delete env
  var to trigger empty-string fallback) and `ORB_ANONYMIZE ?? "true"` (unset →
  defaults to anonymize=true)
- orb-webhook: cover timingSafeEqual throw path (malformed hex sig → catch
  returns false), null closed_at/created_at (time_to_close_ms = null),
  repositories_removed ?? [] fallback, repositories ?? [] fallback on deleted
  event, and if(repos.length) false branch when install list is empty

All five new src/selfhost modules now at 100% statements, branches, functions
and lines.
@JSONbored JSONbored self-assigned this Jun 24, 2026
…nt Host-header redirect attack

Same class of vulnerability as the main setup wizard fix: the Orb wizard
derived the manifest origin from PUBLIC_API_ORIGIN ?? request.url.origin,
allowing an attacker to spoof the Host header and redirect the App-creation
callback to an attacker-controlled domain to steal the Orb App credentials.

Remove the request.url fallback from both setup wizards; return 400 when
PUBLIC_API_ORIGIN is unset.
@JSONbored
JSONbored force-pushed the feat/gittensory-orb branch from 04fd4d0 to 7d692b7 Compare June 24, 2026 11:11
@dosubot dosubot Bot removed the size:XXL label Jun 24, 2026
@dosubot dosubot Bot added the size:L label Jun 24, 2026
@JSONbored JSONbored added the orb Gittensory Orb related - maintainer self-hosting analytics. label Jun 24, 2026
Comment thread src/selfhost/orb-collector.ts
…rop AUTOINCREMENT from Orb migrations

Use the Orb-specific env vars (ORB_WEBHOOK_SECRET, ORB_APP_ID) in exportOrbBatch and instanceId()
instead of the main app's GITHUB_WEBHOOK_SECRET/GITHUB_APP_ID — prevents empty-string HMAC signing
when only the Orb secret is set. Drop AUTOINCREMENT from orb_events/orb_installations DDL so the
Postgres migration adapter accepts both tables (it translates INTEGER PRIMARY KEY but not AUTOINCREMENT).
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@superagent-security superagent-security Bot removed the pr:flagged PR flagged for review by security analysis. label Jun 24, 2026
The pg-dialect translator handles CURRENT_TIMESTAMP and strftime('%f') variants
but not strftime('%S', ...) — the Orb migrations used the latter, breaking the
Postgres integration test. Align with the existing migration convention.
@dosubot dosubot Bot removed size:L labels Jun 24, 2026
@dosubot dosubot Bot added the size:L label Jun 24, 2026
@JSONbored
JSONbored merged commit f90fe5d into main Jun 24, 2026
18 checks passed
@JSONbored
JSONbored deleted the feat/gittensory-orb branch June 24, 2026 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

orb Gittensory Orb related - maintainer self-hosting analytics.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

selfhost(telemetry): Gittensory Orb — outcome signal sidecar + calibration loop

1 participant