githubapp

package
v0.121.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 9, 2026 License: MIT Imports: 23 Imported by: 0

README

Workbench GitHub App control-plane contract

github.com/sneat-dev/wb/api/githubapp owns the typed HTTP contract for the Workbench dashboard at https://sneat.work/bench. The service is mounted by the existing Sneat Go Cloud Run executable at https://wb-github-app.sneat.dev. It is not a separate service.

The host supplies narrow ports:

  1. ReadModel, which records a repository's explicit public opt-in (including its README-linked free-eligibility declaration) before it returns anonymous data. Private subjects require an authenticated member and are rendered as 404 for every other caller.
  2. WorktreeReadModel, whose remote-state provider requires an authenticated member plus MachineAccessResolver approval for every exact publisher.
  3. DeliveryStore, backed by durable storage, which atomically claims GitHub delivery IDs and persists coalesced wakeups.
  4. AuthoritativeReader, which refreshes GitHub App state before a webhook can enqueue work. Cached data is never enough to authorize an action.
  5. MachinePublisherResolver, which resolves an authenticated daemon credential to one exact login/machine pair, and a durable hub.SnapshotStore, which atomically retains the latest validated record for that pair.

The API provides the dashboard summary, repository/organization/user stats, time series usable as tables or graphs, leaderboards, and latest merges with pull request, issue, merge commit, release, and Workbench receipt URLs. The stats route uses a remainder wildcard so canonical IDs such as github.com/acme/app round-trip without dropping path segments.

GET /v0/workbench/worktrees returns one private row per published worktree on the exact machines authorized for the viewer. Query parameters are machine, repository, status, stream, task, and boolean needs_attention. status matches the displayed combined status, lifecycle, or owner status so operators can select attention, review, merged, active, or orphaned without knowing which underlying state supplied it. Rows include the machine, repository, task and stream, branch, lifecycle and owner state, optional pull request, publish and last-activity times, and an attention reason. They never include the snapshot's local path, projects root, prompt, or commit subject. Each row also repeats the machine's effective heartbeat and staleness. An offline machine's last authorized rows remain visible; the default stale window is 24 hours and a provider may configure it without changing stored snapshots.

POST /v0/workbench/machines/snapshot accepts only the separate hosted snapshot schema. The resolver authenticates first, and the request login and machine must exactly match its result. JSON decoding rejects unknown fields and the body is capped at 1 MiB. The service validates bounded strings and at most 5,000 worktrees, computes a payload digest, stamps server receipt time, and calls the store's atomic latest-record operation. Identical retries return the original receipt without writing; older and same-time conflicting payloads cannot replace current state. GET on the same path returns only snapshots for the authenticated login.

The hosted model is an allowlist containing the table's repository, task, stream, branch, lifecycle/owner, pull-request, activity, and attention fields. It cannot encode local paths, projects roots, commit SHAs or subjects, prompts, credentials, repository diagnostics, or command output. RemoteStateWorktreeReadModel consumes the public machinesnapshot.SnapshotStore directly and projects rows without importing or reconstructing any WB CLI remote-state type. The durable adapter uses machinesnapshot.Collection/{machinesnapshot.SnapshotKey(login,machine)}. Each document is exactly machinesnapshot.StoredSnapshot: the allowlisted snapshot map plus server received_at and payload digest. The key is a machine_-prefixed SHA-256 of the validated login, a NUL delimiter, and the validated machine; the source identity remains inside the document for transaction verification.

GET /v0/workbench/events is the default server-to-browser transport. It is resumable SSE: after (or Last-Event-ID) replays durable events with strictly monotonic IDs before the browser receives the live subscription. The source and service filter private events before serialization. Event types are queue, job.phase, job.progress, ci, cleanup, sync, and daemon.generation. WebSocket is reserved for later bidirectional controls such as cancellation and reprioritization.

The sequenced EventSource is also the terminal-monitoring source: filter by repo, task, operation, session, severity, after, and RFC 3339 since. wb monitor --format=jsonl consumes this same sequence; wb log tail can be an alias, but immutable Work Logs are never used as a mutable event queue.

Provider storage boundary

The host-neutral provider in provider.go implements ReadModel over a ProjectionStore; the host supplies the durable adapter. Projection documents use the workbench_projections collection, with scope and the canonical GitHub subject ID (github.com/<org> or github.com/<org>/<repo>) retained in the document. Webhook envelopes are normalized to the same github.com/<org>/<repo> form before wakeups or projection refreshes. ProjectionKey derives a stable SHA-256 document ID so slashes cannot alter storage hierarchy. Series, leaderboard, and latest-merge records use the corresponding named collections and typed store methods. The public latest-merges method is intentionally typed to return public-only entries. Leaderboard documents likewise require public_only; private boards are refused until a subject-scoped board contract exists.

Repository projections are anonymous only when public_opt_in is true. For a private projection the host's MembershipResolver must prove that the Firebase-authenticated Viewer.UserID maps to an installed GitHub identity with access to that exact organization or repository. Browser headers and GitHub repository visibility are never accepted as membership evidence. A missing resolver or failed membership check fails closed as private data.

The provider does not choose Firestore paths, Firebase projects, GitHub credentials, or aggregation credentials. Sneat Go can bind those through its wire-only adapter once the corresponding durable store and membership service are configured.

Authoritative GitHub reader

GitHubRESTProjectionReader is the host-neutral REST adapter for webhook refreshes. The host injects an HTTP transport and installation token source; the reader parses the canonical repository from the delivery, reads the repository, counts pull requests and releases through the GitHub API, and reads the root README.md at the exact commit returned by the README commit query. Only a verified ## WB or ## Workbench opt-in produces public eligibility evidence. A reader implementing AuthoritativeProjectionReader hands the same request-scoped snapshot to the projection engine, so the freshness barrier and projection build do not issue duplicate GitHub reads. Organization projections are omitted until a host supplies an installation-scoped complete aggregation; a public repository count from /orgs/{owner} is not treated as an exact organization summary.

GitHub App installation tokens

InstallationTokenSource is the host-neutral credential boundary used by an authoritative GitHub reader. The host supplies its numeric GitHub App ID, PEM private-key bytes, an http.RoundTripper, and a clock. APIBase is optional and defaults to https://api.github.com; a host can set it for a GitHub Enterprise API or an isolated transport test.

For each verified WebhookDelivery, the source reads only the exact positive installation.id from the JSON payload. It backdates the RS256 GitHub App JWT by one minute for clock skew, expires it nine minutes after the supplied clock, and posts {} to /app/installations/{installation.id}/access_tokens. The exchange accepts only GitHub's 201 Created response with a non-empty, whitespace-safe token and an expires_at later than the same clock reading. Malformed payloads, unsupported or invalid PKCS1/PKCS8 RSA keys, transport failures, oversized or malformed responses, unexpected status codes, and expired tokens fail closed. Errors do not include the PEM key, JWT, token, or response body.

Firestore adapter schema

The host may bind FirestoreProjectionStore, FirestoreProjectionWriter, and FirestoreProjectionDeliveryStore through the small FirestoreBackend seam. Projection documents live in workbench_projections/{ProjectionKey(scope,id)}; series and leaderboards use workbench_series and workbench_leaderboards; the public merge snapshot is workbench_latest_merges/public. Delivery state uses workbench_deliveries/{deliveryID}, and coalesced wakeups use workbench_wakeups/{sha256(wakeupKey)} while retaining the canonical key in the wakeup body. Delivery claims carry a bounded lease and expire into retryable work. Hosts supply the actual Firestore client and transaction implementation; this package contains no Firebase or Firestore SDK dependency.

The public merge document is a bounded, newest-first aggregate. Each eligible repository refresh atomically replaces only that repository's contribution and retains other eligible repositories. A refresh without verified public opt-in removes the repository's earlier contribution without fetching or persisting its private merge details.

Projection delivery boundary

The projector uses a ProjectionDeliveryStore claim before refresh. The claim is atomic across concurrent workers, and ReleaseDelivery makes failed refreshes or writes retryable while preserving the append-only delivery audit. The writer receives the delivery ID with each repository, organization, and latest-merge batch and must make those operations idempotent. The final CommitDeliveryAndWakeup call records the terminal delivery and coalesced wakeup only after all projection writes succeed. A Firestore backend may retry an atomic callback after a conflict; claim and commit outcomes therefore track the final callback attempt rather than an aborted predecessor.

Documentation

Overview

Package githubapp defines the Workbench GitHub App control-plane API.

Index

Constants

View Source
const (
	// ControlPlaneOrigin is the dedicated machine API origin.
	ControlPlaneOrigin = "https://wb-github-app.sneat.dev"
	// ControlPlaneHost is the host-only form used by the Cloud Run adapter.
	ControlPlaneHost = "wb-github-app.sneat.dev"
	// UIOrigin is the browser origin permitted to call the control-plane API.
	UIOrigin = "https://sneat.work"
	// APIPrefix is mounted by the host application.
	APIPrefix = "/v0/workbench"
)
View Source
const (
	ProjectionCollection  = "workbench_projections"
	SeriesCollection      = "workbench_series"
	LeaderboardCollection = "workbench_leaderboards"
	MergeCollection       = "workbench_latest_merges"
)
View Source
const DefaultMachineStaleAfter = 24 * time.Hour

DefaultMachineStaleAfter matches WB's ordinary remote-machine freshness window. Hosted views keep stale rows visible; this threshold only labels their last published observation.

Variables

View Source
var (
	ErrPublisherIdentity = errors.New("machine publisher identity is unavailable")
	ErrPublisherMismatch = errors.New("published login or machine does not match the authenticated publisher")
)
View Source
var (
	ErrNoProjector       = errors.New("workbench projection engine is not configured")
	ErrInvalidProjection = errors.New("invalid workbench projection snapshot")
)
View Source
var (
	ErrPrivateData = errors.New("private Workbench data requires membership")
	ErrNoReadModel = errors.New("workbench read model is not configured")
	ErrNoWebhook   = errors.New("workbench webhook processor is not configured")
)
View Source
var ErrProjectionNotFound = errors.New("workbench projection not found")

Functions

func NewHandler

func NewHandler(options HandlerOptions) http.Handler

NewHandler returns the Workbench GitHub App API under APIPrefix. It permits the Sneat Workbench browser origin only; GitHub webhooks have no CORS need.

func ProjectionKey added in v0.100.0

func ProjectionKey(scope Scope, id string) string

ProjectionKey is the stable document key: scope plus a SHA-256 digest of the canonical subject ID. The digest prevents slashes in github.com/org/repo IDs from changing collection hierarchy while preserving the original ID in the document body for audit and display.

func ValidateProjectionDocument added in v0.100.0

func ValidateProjectionDocument(document ProjectionDocument) error

func ValidatePublicEligibility added in v0.102.0

func ValidatePublicEligibility(evidence PublicEligibility) error

ValidatePublicEligibility rejects incomplete or non-canonical durable evidence. It intentionally verifies the identity and URL shape only; the authoritative reader verifies the README contents before it records this evidence with a projection.

Types

type Access

type Access[T any] struct {
	Visibility Visibility
	Value      T
}

Access wraps a read-model result with its disclosure class.

type AuthoritativeProjectionReader added in v0.103.0

type AuthoritativeProjectionReader interface {
	RefreshAuthoritativeProjection(context.Context, WebhookDelivery) (ProjectionSnapshot, error)
}

AuthoritativeProjectionReader combines the freshness barrier and projection read for readers that can carry one request-scoped GitHub snapshot across the provider boundary. This avoids issuing the same REST reads twice.

type AuthoritativeReader

type AuthoritativeReader interface {
	Refresh(context.Context, WebhookDelivery) error
}

AuthoritativeReader refreshes the GitHub App's authoritative view before an action is queued. A cache hit alone must never satisfy this call.

type Dashboard

type Dashboard struct {
	GeneratedAt time.Time `json:"generated_at"`
	Summary     Summary   `json:"summary"`
	Links       []Link    `json:"links,omitempty"`
}

Dashboard is the top-level dashboard response.

type DeliveryStore

type DeliveryStore interface {
	HasDelivery(context.Context, string) (bool, error)
	CommitDeliveryAndWakeup(context.Context, string, Wakeup) (bool, error)
}

DeliveryStore must be backed by durable storage. HasDelivery is a cheap preflight that avoids repeating an authoritative GitHub read for a redelivery. CommitDeliveryAndWakeup atomically records the delivery ID and persists a wakeup coalesced by its scope key. It returns false when a concurrent request committed the same delivery first.

type Event

type Event struct {
	ID         uint64          `json:"id"`
	Type       EventType       `json:"type"`
	Visibility Visibility      `json:"visibility"`
	At         time.Time       `json:"at"`
	Repository string          `json:"repository,omitempty"`
	Task       string          `json:"task,omitempty"`
	Operation  string          `json:"operation,omitempty"`
	Session    string          `json:"session,omitempty"`
	Severity   string          `json:"severity,omitempty"`
	Payload    json.RawMessage `json:"payload"`
}

Event is an SSE record. ID is a durable monotonic cursor, not a timestamp. Payload is only serialized after the viewer passes its visibility check.

type EventFilter

type EventFilter struct {
	After      uint64
	Since      time.Time
	Repository string
	Task       string
	Operation  string
	Session    string
	Severity   string
}

EventFilter selects one resumable daemon/direct-WB event sequence. Work Logs remain immutable per-task evidence; this filter is transient monitoring only.

type EventSource

type EventSource interface {
	Replay(context.Context, EventFilter) ([]Event, error)
	Subscribe(context.Context, EventFilter) (<-chan Event, error)
}

EventSource durably replays events strictly after a cursor, then exposes a live subscription. Implementations must retain enough history for reconnects and issue globally monotonic IDs across daemon generations.

type EventType

type EventType string

EventType classifies a live Workbench daemon update. WebSocket is reserved for later bidirectional controls such as cancel and reprioritize.

const (
	EventQueue            EventType = "queue"
	EventJobPhase         EventType = "job.phase"
	EventJobProgress      EventType = "job.progress"
	EventCI               EventType = "ci"
	EventCleanup          EventType = "cleanup"
	EventSync             EventType = "sync"
	EventDaemonGeneration EventType = "daemon.generation"
)

type FirestoreBackend added in v0.102.0

type FirestoreBackend interface {
	Get(context.Context, string, string, any) (bool, error)
	Query(context.Context, string, map[string]any, int, any) error
	Set(context.Context, string, string, any) error
	UpdateAtomic(context.Context, func(FirestoreTransaction) error) error
}

FirestoreBackend is the deliberately small seam implemented by the host's Firestore client. Values are decoded into out by the backend. UpdateAtomic must provide Firestore transaction semantics and may invoke its callback more than once when the storage engine retries a conflict.

type FirestoreProjectionDeliveryStore added in v0.102.0

type FirestoreProjectionDeliveryStore struct {
	Backend FirestoreBackend
	Now     func() time.Time
	Lease   time.Duration
}

FirestoreProjectionDeliveryStore provides the atomic claim and coalesced wakeup used by ProjectionEngine. An expired claim is recoverable.

func (FirestoreProjectionDeliveryStore) ClaimDelivery added in v0.102.0

func (store FirestoreProjectionDeliveryStore) ClaimDelivery(ctx context.Context, id string) (bool, error)

func (FirestoreProjectionDeliveryStore) CommitDeliveryAndWakeup added in v0.102.0

func (store FirestoreProjectionDeliveryStore) CommitDeliveryAndWakeup(ctx context.Context, id string, wakeup Wakeup) (bool, error)

func (FirestoreProjectionDeliveryStore) HasDelivery added in v0.102.0

func (store FirestoreProjectionDeliveryStore) HasDelivery(ctx context.Context, id string) (bool, error)

func (FirestoreProjectionDeliveryStore) ReleaseDelivery added in v0.102.0

func (store FirestoreProjectionDeliveryStore) ReleaseDelivery(ctx context.Context, id string) error

type FirestoreProjectionStore added in v0.102.0

type FirestoreProjectionStore struct{ Backend FirestoreBackend }

FirestoreProjectionStore maps the documented Workbench collections to the host backend. The adapter contains no cloud.google.com imports, keeping the provider portable and letting Sneat Go supply its configured client.

func (FirestoreProjectionStore) GetLeaderboard added in v0.102.0

func (store FirestoreProjectionStore) GetLeaderboard(ctx context.Context, metric string) (LeaderboardDocument, error)

func (FirestoreProjectionStore) GetProjection added in v0.102.0

func (store FirestoreProjectionStore) GetProjection(ctx context.Context, scope Scope, id string) (ProjectionDocument, error)

func (FirestoreProjectionStore) ListProjections added in v0.102.0

func (store FirestoreProjectionStore) ListProjections(ctx context.Context, scope Scope) ([]ProjectionDocument, error)

func (FirestoreProjectionStore) ListPublicLatestMerges added in v0.102.0

func (store FirestoreProjectionStore) ListPublicLatestMerges(ctx context.Context, limit int) (PublicLatestMerges, error)

func (FirestoreProjectionStore) ListSeries added in v0.102.0

func (store FirestoreProjectionStore) ListSeries(ctx context.Context, scope Scope, id, metric string) (SeriesDocument, error)

type FirestoreProjectionWriter added in v0.102.0

type FirestoreProjectionWriter struct{ Backend FirestoreBackend }

FirestoreProjectionWriter applies complete batches by stable document key. Set is intentionally idempotent, so a retry after a crash cannot duplicate projections or public merges.

func (FirestoreProjectionWriter) WriteLatestMerges added in v0.102.0

func (writer FirestoreProjectionWriter) WriteLatestMerges(ctx context.Context, deliveryID string, batch RepositoryLatestMerges) error

func (FirestoreProjectionWriter) WriteOrganizations added in v0.102.0

func (writer FirestoreProjectionWriter) WriteOrganizations(ctx context.Context, deliveryID string, records []ProjectionDocument) error

func (FirestoreProjectionWriter) WriteRepositories added in v0.102.0

func (writer FirestoreProjectionWriter) WriteRepositories(ctx context.Context, deliveryID string, records []ProjectionDocument) error

type FirestoreTransaction added in v0.102.0

type FirestoreTransaction interface {
	Get(context.Context, string, string, any) (bool, error)
	Set(context.Context, string, string, any) error
}

type GitHubRESTProjectionReader added in v0.103.0

type GitHubRESTProjectionReader struct {
	HTTP    HTTPDoer
	Tokens  GitHubTokenSource
	APIBase string
	Now     func() time.Time
}

GitHubRESTProjectionReader builds one delivery snapshot from GitHub's REST API. APIBase is injectable for a host proxy and tests; the default is the public GitHub API.

func (GitHubRESTProjectionReader) Refresh added in v0.103.0

func (reader GitHubRESTProjectionReader) Refresh(ctx context.Context, delivery WebhookDelivery) error

func (GitHubRESTProjectionReader) RefreshAuthoritativeProjection added in v0.103.0

func (reader GitHubRESTProjectionReader) RefreshAuthoritativeProjection(ctx context.Context, delivery WebhookDelivery) (ProjectionSnapshot, error)

func (GitHubRESTProjectionReader) RefreshProjection added in v0.103.0

func (reader GitHubRESTProjectionReader) RefreshProjection(ctx context.Context, delivery WebhookDelivery) (ProjectionSnapshot, error)

type GitHubTokenSource added in v0.103.0

type GitHubTokenSource interface {
	Token(context.Context, WebhookDelivery) (string, error)
}

type HTTPDoer added in v0.103.0

type HTTPDoer interface {
	Do(*http.Request) (*http.Response, error)
}

HTTPDoer and GitHubTokenSource keep credentials and transport in the host. The reader never imports a GitHub SDK or accepts identity from a webhook payload beyond its repository/install identity.

type HandlerOptions

type HandlerOptions struct {
	Service           Service
	ViewerResolver    ViewerResolver
	PublisherResolver MachinePublisherResolver
	MachineSnapshots  *MachineSnapshotService
	AllowedOrigin     string
}

HandlerOptions supplies the narrow host bindings for the public API.

type InstallationTokenSource added in v0.104.0

type InstallationTokenSource struct {
	AppID         int64
	PrivateKeyPEM []byte
	Transport     http.RoundTripper
	APIBase       string
	Now           func() time.Time
}

InstallationTokenSource exchanges a delivery's exact GitHub App installation identity for a short-lived installation access token. Hosts supply every credential, the HTTP transport, and the clock.

func (InstallationTokenSource) Token added in v0.104.0

func (source InstallationTokenSource) Token(ctx context.Context, delivery WebhookDelivery) (string, error)

Token creates a short-lived RS256 GitHub App JWT and exchanges it for the installation token identified by delivery.Payload. Errors never include the private key, JWT, installation token, or response body.

type LatestMerge

type LatestMerge struct {
	Repository     string    `json:"repository" firestore:"repository"`
	PullRequest    int       `json:"pull_request" firestore:"pull_request"`
	MergedAt       time.Time `json:"merged_at" firestore:"merged_at"`
	PullRequestURL string    `json:"pull_request_url,omitempty" firestore:"pull_request_url,omitempty"`
	IssueURL       string    `json:"issue_url,omitempty" firestore:"issue_url,omitempty"`
	MergeCommitSHA string    `json:"merge_commit_sha,omitempty" firestore:"merge_commit_sha,omitempty"`
	MergeCommitURL string    `json:"merge_commit_url,omitempty" firestore:"merge_commit_url,omitempty"`
	ReleaseURL     string    `json:"release_url,omitempty" firestore:"release_url,omitempty"`
	ReceiptURL     string    `json:"receipt_url,omitempty" firestore:"receipt_url,omitempty"`
}

LatestMerge gives the dashboard every navigable artifact around a merge.

type Leaderboard

type Leaderboard struct {
	Metric  string             `json:"metric"`
	Entries []LeaderboardEntry `json:"entries"`
}

Leaderboard groups ranked values for a requested metric.

type LeaderboardDocument added in v0.100.0

type LeaderboardDocument struct {
	Metric     string             `json:"metric" firestore:"metric"`
	Entries    []LeaderboardEntry `json:"entries" firestore:"entries"`
	PublicOnly bool               `json:"public_only" firestore:"public_only"`
}

type LeaderboardEntry

type LeaderboardEntry struct {
	Rank        int    `json:"rank" firestore:"rank"`
	SubjectID   string `json:"subject_id" firestore:"subject_id"`
	DisplayName string `json:"display_name" firestore:"display_name"`
	Value       int64  `json:"value" firestore:"value"`
}

LeaderboardEntry is intentionally small so public leaderboards do not leak private repository names or private activity counts.

type Link struct {
	Kind string `json:"kind"`
	Href string `json:"href"`
}

Link is a canonical GitHub, release, or Workbench receipt reference.

type MachineAccessResolver added in v0.112.0

type MachineAccessResolver interface {
	CanViewMachine(context.Context, Viewer, string, string) (bool, error)
}

MachineAccessResolver proves that one viewer may inspect one exact published machine. Authentication alone is never treated as machine ownership.

type MachinePublisher added in v0.114.0

type MachinePublisher struct {
	Login   string
	Machine string
}

MachinePublisher is the exact identity bound to a daemon credential by the host. Neither value comes from request headers or the submitted payload.

type MachinePublisherResolver added in v0.114.0

type MachinePublisherResolver interface {
	Publisher(*http.Request) (MachinePublisher, error)
}

MachinePublisherResolver binds host authentication to one exact WB machine.

type MachineSnapshotService added in v0.114.0

type MachineSnapshotService struct {
	Store machinesnapshot.SnapshotStore
	Now   func() time.Time
}

MachineSnapshotService applies publisher identity, validation, server time, and privacy-safe persistence around a durable store.

func (MachineSnapshotService) List added in v0.114.0

List returns every durable machine for the authenticated login. Publisher credentials never grant cross-login reads.

func (MachineSnapshotService) Publish added in v0.114.0

Publish validates and atomically stores the latest snapshot for one exact authenticated login/machine key.

type MembershipResolver added in v0.100.0

type MembershipResolver interface {
	Member(context.Context, Viewer, Scope, string) (bool, error)
}

MembershipResolver proves GitHub installation membership for one canonical subject. A Firebase-authenticated host maps Viewer.UserID to GitHub identity; the provider never trusts browser headers or public GitHub visibility.

type ProjectionDeliveryStore added in v0.101.0

type ProjectionDeliveryStore interface {
	DeliveryStore
	ClaimDelivery(context.Context, string) (bool, error)
	ReleaseDelivery(context.Context, string) error
}

ProjectionDeliveryStore extends DeliveryStore with an atomic in-flight claim. ClaimDelivery returns false for a committed or currently claimed delivery. ReleaseDelivery makes refresh/write failures retryable; the implementation must retain its append-only audit record.

type ProjectionDocument added in v0.100.0

type ProjectionDocument struct {
	Scope             Scope              `json:"scope" firestore:"scope"`
	ID                string             `json:"id" firestore:"id"`
	DisplayName       string             `json:"display_name" firestore:"display_name"`
	Summary           Summary            `json:"summary" firestore:"summary"`
	UpdatedAt         time.Time          `json:"updated_at" firestore:"updated_at"`
	PublicOptIn       bool               `json:"public_opt_in" firestore:"public_opt_in"`
	PublicEligibility *PublicEligibility `json:"public_eligibility,omitempty" firestore:"public_eligibility,omitempty"`
}

ProjectionDocument is the durable, privacy-classified summary written by a Workbench-owned projector. Public responses require PublicOptIn; private responses require the host membership resolver below.

type ProjectionEngine added in v0.101.0

type ProjectionEngine struct {
	Deliveries          ProjectionDeliveryStore
	Reader              ProjectionReader
	Writer              ProjectionWriter
	AuthoritativeReader AuthoritativeReader
	WebhookSecret       []byte
}

ProjectionEngine coordinates authoritative refresh, durable projection writes, and delivery receipt publication. It deliberately does not import GitHub, Firebase, or Firestore clients.

func (ProjectionEngine) Process added in v0.101.0

func (engine ProjectionEngine) Process(ctx context.Context, delivery WebhookDelivery, signature string) (queued bool, processErr error)

Process verifies and applies one delivery. AuthoritativeReader is retained as a required companion to ProjectionReader so hosts cannot accidentally wire a projection reader that omits the existing refresh barrier.

type ProjectionReader added in v0.101.0

type ProjectionReader interface {
	RefreshProjection(context.Context, WebhookDelivery) (ProjectionSnapshot, error)
}

ProjectionReader refreshes and returns the authoritative records for a delivery. Implementations may coalesce equivalent repository refreshes, but must not satisfy a delivery from cache alone.

type ProjectionSnapshot added in v0.101.0

type ProjectionSnapshot struct {
	Repositories  []ProjectionDocument
	Organizations []ProjectionDocument
	LatestMerges  *RepositoryLatestMerges
}

ProjectionSnapshot is the authoritative result of refreshing one GitHub App delivery. The reader owns GitHub access; the provider owns validation and the stable projection write order.

type ProjectionStore added in v0.100.0

type ProjectionStore interface {
	ListProjections(context.Context, Scope) ([]ProjectionDocument, error)
	GetProjection(context.Context, Scope, string) (ProjectionDocument, error)
	ListSeries(context.Context, Scope, string, string) (SeriesDocument, error)
	GetLeaderboard(context.Context, string) (LeaderboardDocument, error)
	ListPublicLatestMerges(context.Context, int) (PublicLatestMerges, error)
}

ProjectionStore is the narrow durable persistence adapter supplied by the host. Implementations map these operations to Firestore or another durable store; aggregation and disclosure stay in this package.

type ProjectionWriter added in v0.101.0

type ProjectionWriter interface {
	WriteRepositories(context.Context, string, []ProjectionDocument) error
	WriteOrganizations(context.Context, string, []ProjectionDocument) error
	WriteLatestMerges(context.Context, string, RepositoryLatestMerges) error
}

ProjectionWriter durably applies a complete authoritative snapshot. Every method is keyed by deliveryID and must be idempotent: a crash after a write and before DeliveryStore commits is safe to retry. Implementations should replace projections by ProjectionKey and replace the public merge view as a single logical operation.

type PublicEligibility

type PublicEligibility struct {
	Repository string    `json:"repository" firestore:"repository"`
	READMEURL  string    `json:"readme_url" firestore:"readme_url"`
	VerifiedAt time.Time `json:"verified_at" firestore:"verified_at"`
}

PublicEligibility is the auditable root-README opt-in record required before a repository can appear in unauthenticated results. It is not inferred from GitHub repository visibility alone.

func VerifyPublicEligibility added in v0.102.0

func VerifyPublicEligibility(repository, readmeURL, markdown string, verifiedAt time.Time) (PublicEligibility, error)

VerifyPublicEligibility returns auditable evidence only when the root README has an explicit Workbench opt-in. The caller supplies the canonical GitHub README URL it read and the verification time from its authoritative refresh.

type PublicLatestMerges added in v0.100.0

type PublicLatestMerges struct {
	Entries []LatestMerge `json:"entries" firestore:"entries"`
}

type ReadModel

ReadModel owns persistence and GitHub data projection. It must return only subjects whose public opt-in is recorded, unless the supplied viewer is an authenticated member of the private subject.

type RemoteStateWorktreeReadModel added in v0.112.0

type RemoteStateWorktreeReadModel struct {
	Store      machinesnapshot.SnapshotStore
	Access     MachineAccessResolver
	Now        func() time.Time
	StaleAfter time.Duration
}

RemoteStateWorktreeReadModel projects existing WB machine snapshots through a per-machine authorization boundary.

func (RemoteStateWorktreeReadModel) Worktrees added in v0.112.0

type RepositoryLatestMerges added in v0.104.2

type RepositoryLatestMerges struct {
	Repository  string
	PublicOptIn bool
	Entries     []LatestMerge
}

RepositoryLatestMerges is one repository's complete contribution to the anonymous latest-merge view. PublicOptIn false removes any earlier public contribution for the repository without publishing private merge details.

type Scope

type Scope string

Scope identifies the GitHub subject represented by a statistic.

const (
	ScopeRepository   Scope = "repository"
	ScopeOrganization Scope = "organization"
	ScopeUser         Scope = "user"
)

type Series

type Series struct {
	Scope  Scope         `json:"scope"`
	ID     string        `json:"id"`
	Metric string        `json:"metric"`
	Points []SeriesPoint `json:"points"`
}

Series is a named time-series for graph and table consumers.

type SeriesDocument added in v0.100.0

type SeriesDocument struct {
	Scope  Scope         `json:"scope" firestore:"scope"`
	ID     string        `json:"id" firestore:"id"`
	Metric string        `json:"metric" firestore:"metric"`
	Points []SeriesPoint `json:"points" firestore:"points"`
}

type SeriesPoint

type SeriesPoint struct {
	At    time.Time `json:"at" firestore:"at"`
	Value int64     `json:"value" firestore:"value"`
}

SeriesPoint can render either a graph point or a table row.

type Service

type Service struct {
	// Projector is the only supported webhook processor.
	Projector *ProjectionEngine
	ReadModel ReadModel
	Worktrees WorktreeReadModel
	Events    EventSource
}

Service applies disclosure policy around a Workbench read model and processes signed GitHub App webhook deliveries.

func (Service) Dashboard

func (service Service) Dashboard(ctx context.Context, viewer Viewer) (Dashboard, error)

func (Service) EventStream

func (service Service) EventStream(ctx context.Context, viewer Viewer, filter EventFilter) ([]Event, <-chan Event, error)

EventStream replays visible durable events after cursor and returns the filtered live channel. It validates monotonic order so a bad source cannot cause a browser to skip or regress a reconnect cursor.

func (Service) LatestMerges

func (service Service) LatestMerges(ctx context.Context, viewer Viewer, limit int) ([]LatestMerge, error)

func (Service) Leaderboard

func (service Service) Leaderboard(ctx context.Context, viewer Viewer, metric string) (Leaderboard, error)

func (Service) ProcessWebhook

func (service Service) ProcessWebhook(ctx context.Context, delivery WebhookDelivery, signature string) (bool, error)

ProcessWebhook delegates webhook processing to the claim-safe projection engine. Hosts without a configured projector fail closed; the legacy fields are retained only so migrating compositions remain source-compatible.

func (Service) Series

func (service Service) Series(ctx context.Context, viewer Viewer, scope Scope, id, metric string) (Series, error)

func (Service) Stats

func (service Service) Stats(ctx context.Context, viewer Viewer, scope Scope, id string) (Stat, error)

func (Service) WorktreeTable added in v0.112.0

func (service Service) WorktreeTable(ctx context.Context, viewer Viewer, filter WorktreeFilter) (WorktreeTable, error)

WorktreeTable returns one privacy-safe row per published worktree on the machines the host authorizes for this viewer.

type Stat

type Stat struct {
	Scope       Scope     `json:"scope"`
	ID          string    `json:"id"`
	DisplayName string    `json:"display_name"`
	Summary     Summary   `json:"summary"`
	UpdatedAt   time.Time `json:"updated_at"`
	Links       []Link    `json:"links,omitempty"`
}

Stat is one scoped repository, organization, or user result.

type StoreReadModel added in v0.100.0

type StoreReadModel struct {
	Store      ProjectionStore
	Membership MembershipResolver
}

func (StoreReadModel) Dashboard added in v0.100.0

func (model StoreReadModel) Dashboard(ctx context.Context, viewer Viewer) (Access[Dashboard], error)

func (StoreReadModel) LatestMerges added in v0.100.0

func (model StoreReadModel) LatestMerges(ctx context.Context, viewer Viewer, limit int) (Access[[]LatestMerge], error)

func (StoreReadModel) Leaderboard added in v0.100.0

func (model StoreReadModel) Leaderboard(ctx context.Context, viewer Viewer, metric string) (Access[Leaderboard], error)

func (StoreReadModel) Series added in v0.100.0

func (model StoreReadModel) Series(ctx context.Context, viewer Viewer, scope Scope, id, metric string) (Access[Series], error)

func (StoreReadModel) Stats added in v0.100.0

func (model StoreReadModel) Stats(ctx context.Context, viewer Viewer, scope Scope, id string) (Access[Stat], error)

type Summary

type Summary struct {
	Repositories int `json:"repositories" firestore:"repositories"`
	OpenPulls    int `json:"open_pulls" firestore:"open_pulls"`
	MergedPulls  int `json:"merged_pulls" firestore:"merged_pulls"`
	OpenIssues   int `json:"open_issues" firestore:"open_issues"`
	Releases     int `json:"releases" firestore:"releases"`
}

Summary is the compact dashboard card set.

type Viewer

type Viewer struct {
	Authenticated bool
	Member        bool
	UserID        string
}

Viewer is resolved by the host before a private record is rendered.

type ViewerResolver

type ViewerResolver interface {
	Viewer(*http.Request) (Viewer, error)
}

ViewerResolver binds the host authentication and membership system to the Workbench domain. The WB domain never accepts identity headers directly.

type Visibility

type Visibility string

Visibility describes whether a response contains an explicitly opted-in public subject or a member-only private subject.

const (
	VisibilityPublic  Visibility = "public"
	VisibilityPrivate Visibility = "private"
)

type Wakeup

type Wakeup struct {
	Key        string `json:"key" firestore:"key"`
	Repository string `json:"repository" firestore:"repository"`
	Event      string `json:"event" firestore:"event"`
}

Wakeup is a durable, coalescible unit of refresh work.

type WebhookDelivery

type WebhookDelivery struct {
	ID         string
	Event      string
	Repository string
	Payload    []byte
}

WebhookDelivery is the verified, minimally parsed GitHub webhook envelope.

type WorktreeFilter added in v0.112.0

type WorktreeFilter struct {
	Machine        string
	Repository     string
	Status         string
	Stream         string
	Task           string
	NeedsAttention *bool
}

WorktreeFilter selects rows in the consolidated development-machine table. Empty strings match every value. NeedsAttention is a pointer so false can be selected explicitly rather than being confused with an omitted filter.

type WorktreeReadModel added in v0.112.0

type WorktreeReadModel interface {
	Worktrees(context.Context, Viewer, WorktreeFilter) (Access[WorktreeTable], error)
}

WorktreeReadModel supplies the private cross-machine dashboard table.

type WorktreeRow added in v0.112.0

type WorktreeRow struct {
	Repository      string     `json:"repository"`
	Task            string     `json:"task"`
	Stream          string     `json:"stream,omitempty"`
	Branch          string     `json:"branch"`
	Status          string     `json:"status"`
	Lifecycle       string     `json:"lifecycle"`
	OwnerStatus     string     `json:"owner_status"`
	Owner           string     `json:"owner,omitempty"`
	PullRequest     int        `json:"pull_request,omitempty"`
	PullRequestURL  string     `json:"pull_request_url,omitempty"`
	Machine         string     `json:"machine"`
	MachineSeenAt   time.Time  `json:"machine_seen_at"`
	MachineStale    bool       `json:"machine_stale"`
	LastActivityAt  *time.Time `json:"last_activity_at,omitempty"`
	PublishedAt     time.Time  `json:"published_at"`
	NeedsAttention  bool       `json:"needs_attention"`
	AttentionReason string     `json:"attention_reason,omitempty"`
}

WorktreeRow is the privacy-safe hosted projection of a published worktree. It intentionally has no local path, projects root, commit subject, or prompt.

type WorktreeTable added in v0.112.0

type WorktreeTable struct {
	GeneratedAt time.Time     `json:"generated_at"`
	Rows        []WorktreeRow `json:"rows"`
}

WorktreeTable is one generated, filterable view across authorized machines.

Directories

Path Synopsis
Package machinesnapshot defines the public, privacy-safe HTTP and durable storage contract for hosted WB machine state.
Package machinesnapshot defines the public, privacy-safe HTTP and durable storage contract for hosted WB machine state.
Package repositoryevent defines the public, privacy-safe contract used by the Workbench GitHub App to notify enrolled WB daemons about repository changes.
Package repositoryevent defines the public, privacy-safe contract used by the Workbench GitHub App to notify enrolled WB daemons about repository changes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL