25 KiB
Changelog
All notable changes to ForgeBucket are documented here.
Format follows Keep a Changelog. Versions follow Semantic Versioning.
Unreleased
Planned — Phase 5 (AI Diagnostics + Deployment Promotions + Rollback Visualization)
- AI-powered pipeline failure diagnostics
- Deployment promotion workflows (manual + automated)
- Rollback visualization and timeline
1.0.0 — 2026-05-13
Phase 4 complete. SBOM generation, secret scanning, dependency vulnerability scanning, signed artifacts, and OCI registry are operational.
Added — SBOM Generation (internal/domain/sbom/)
Generator— subscribes topipeline.completedevents and auto-generates CycloneDX 1.4 SBOM documents for every successful pipeline run; also supports on-demand generation viaGenerateOnDemand- 6 manifest parsers:
go.mod,package.json,requirements.txt,Cargo.toml,Gemfile.lock,pom.xml— lightweight line-scanning, no external parser dependencies - API endpoints —
GET /sbom,GET /sbom/document,GET /runs/{runID}/sbom,GET /runs/{runID}/sbom/document,POST /sbom/generate?ref=&runID= - Database — migration
016_sbomaddsSBOMReportmodel with CycloneDX document body - Automatic generation on pipeline completion now also fires directly from the orchestrator (not solely via NATS), ensuring SBOMs are generated even when NATS is unavailable
Added — Secret Scanning (internal/domain/scanning/)
Scanner— subscribes topush.receivedevents, scans git diffs against 15 regex patterns for high/medium severity secrets- Secret patterns: AWS keys, GitHub/GitLab tokens, generic API keys, Bearer tokens, Slack tokens, Google API keys, Google service accounts, SSH private keys, JWTs, NPM tokens, PostgreSQL/Redis connection strings, generic passwords
- API endpoints —
GET /secrets/leaks,POST /secrets/leaks/{leakID}/dismiss(repo-scoped),GET /api/v1/secrets/leaks(global admin) - Database — migration
018_scanningaddsSecretLeakmodel
Added — Vulnerability Scanning (internal/domain/vulnscan/)
Scanner— triggers on-demand scans against the OSV API (api.osv.dev/v1); supports scanning by PURL or by fetching the latest SBOM and scanning all components- OSV client — HTTP client with 30-second timeout, queries OSV database for CVEs by PURL or ecosystem+name, extracts CVSS scores and fixed version ranges
- API endpoints —
GET /vulnerabilities,POST /vulnerabilities/scan,POST /vulnerabilities/{findingID}/dismiss(repo-scoped),GET /api/v1/vulnerabilities(global admin) - Database — migration
019_vulnscanaddsVulnerabilityFindingmodel - Findings deduplicated by
(vuln_id, purl, repo_id)
Added — Artifact Signing (internal/domain/signing/)
KeyStore— ECDSA P-256 signing and verification; produces self-verifyingBundlecarrying payload, signature (ASN.1 DER), and public key PEMSign(artifactID, name, rawContent)— computes SHA-256 digest, signs, returns signedBundlewith key ID fingerprintVerify(bundleJSON)— extracts public key from bundle, verifies ECDSA signature, returnsVerifyResultwith key-matching checkGenerate()— creates ephemeral ECDSA P-256 key whenARTIFACT_SIGNING_KEYenv var is unset (logs warning; signatures lost on restart unless persisted)- API endpoints —
GET /artifacts/{artifactID}/signature,GET /artifacts/{artifactID}/verify - Database — migration
015_signingaddsArtifactSignaturemodel
Added — OCI Registry (internal/domain/oci/)
Registry— content-addressable on-disk blob store implementing OCI Distribution Spec v1.1- Storage layout:
{root}/blobs/sha256/<hex>for blobs,{root}/uploads/<uuid>for in-progress uploads - Full upload session lifecycle: start (POST), append chunk (PATCH), finalize with digest verification (PUT), cancel (DELETE), offset query (GET)
- 13 OCI distribution error codes defined (
ErrBlobUnknown,ErrDigestInvalid,ErrManifestInvalid, etc.) - API handlers (
internal/api/handlers/oci.go, 525 lines) — full/v2/{name}/{kind}/{ref}routing: manifest push/get/delete, blob HEAD/get/delete, tag listing, chunked upload - Database — migration
017_ociaddsOCIRepository,OCIManifest,OCITag,OCIBlob,OCIUploadmodels - Registry is consumed by standard OCI tools (Docker, Podman, Skopeo, containerd)
Added — Unified Security Page (/repos/:owner/:repo/security)
RepoSecurityPage— single-page view combining SBOM status, secret leak detection, and vulnerability findings- SBOM section: displays existing SBOM metadata with download button, or "Generate SBOM" form with branch/SHA input
- Secret Leaks section: lists leaks with severity badge, pattern name, commit SHA, ref, match sample, dismiss button
- Vulnerabilities section: lists findings with CVSS severity (CRITICAL/HIGH/MEDIUM/LOW), vuln ID, score, summary, PURL, version, fix suggestion, dismiss button; "Scan now" trigger
- Route added in
App.tsx, nav link added inRepoPage.tsxtab bar
Added — Pipeline Run SBOM Integration
PipelineRunPageshows per-run SBOM section: metadata (components, SHA, generation time) + download button- "Generate SBOM" button for completed/failed runs that lack one
useRunSBOM/useGenerateSBOMhooks infrontend/src/api/queries/sbom.ts- 404 from
useRunSBOMhandled gracefully (returnsnullinstead of throwing)
Added — Database Models
- Migration
016_sbom—SBOMReport(repoId, runId, sha, format, componentCount, bomDocument, generatedAt) - Migration
017_oci—OCIRepository,OCIManifest,OCITag,OCIBlob,OCIUpload - Migration
018_scanning—SecretLeak(repoId, commitSha, ref, patternName, description, severity, matchSample, dismissed, dismissedBy, dismissedAt, detectedAt) - Migration
019_vulnscan—VulnerabilityFinding(repoId, vulnId, purl, version, summary, details, cvssScore, fixedVersion, dismissed, dismissedBy, dismissedAt, detectedAt) - Migration
020_forgefed— repository + pull request column updates
Fixed
- SBOM per-run download endpoint (
/runs/{runID}/sbom/document) was registered at the wrong router nesting level, causing a route conflict with theGetLatestDocumenthandler. Moved into the correct/runs/{runID}route block. usernamecontext key extraction in scanning and vulnerability handlers changed from raw string"user"to typedmiddleware.ContextKeyUsername- Nil-safe
Needsmarshalling in orchestrator job creation - Nil-safe findings response in vulnerability scan API
GenerateOnDemandSBOM cache key now includesrunIDto prevent per-run generation from being shadowed by prior on-demand generation
0.9.0 — 2026-05-12
Phase 3F complete. ForgeBucket is now a first-class ActivityPub node — interoperable with Mastodon, Forgejo, and any fediverse server.
Added — ActivityPub Federation (internal/domain/federation/)
GET /.well-known/webfinger— resolvesacct:user@domainto the actor URL; returnsapplication/jrd+jsonGET /users/{username}— returns a JSON-LD actor document (Persontype) including public key object for HTTP signature verificationPOST /users/{username}/inbox— receives and dispatches inbound ActivityPub activities; HTTP signature verification enforced in production (skipped inDEBUG=truemode for local testing)GET /users/{username}/outbox— serves anOrderedCollection(summary on page 0, paginatedOrderedCollectionPageon page ≥ 1, 20 activities per page)GET /users/{username}/followers— stubOrderedCollection(zero items; social graph in Phase 4)GET /users/{username}/following— stubOrderedCollection
Added — HTTP Signatures (internal/domain/federation/signatures.go)
Sign(req, keyID, privateKeyPEM)— signs outgoing HTTP requests with RSA-SHA256; covers(request-target),host, anddateheadersVerify(r, db, instanceURL)— parsesSignatureheader, resolves sender's public key (localFederationActorfirst, then network fetch viaFetchActor), verifies RSA-SHA256 digest
Added — Actor Lifecycle (internal/domain/federation/actor.go)
GetOrCreate— lazily creates aFederationActorfor a local user; generates a fresh RSA-2048 key pair and derivesInboxURL,OutboxURL,APIDfromINSTANCE_URL; stable across requestsActorJSON— returns the JSON-LD document shape expected by all ActivityPub clientsAPID(instanceURL, username)— canonical{instanceURL}/users/{username}helper
Added — Follow / Accept Flow (internal/domain/federation/inbox.go)
- Incoming
Followactivities are auto-accepted: remote actor is fetched (or retrieved from cache), anAcceptactivity is signed and delivered to their inbox asynchronously - Both the inbound
Followand outboundAcceptare persisted toFederationActivityfor audit
Added — Remote Actor Cache (internal/domain/federation/remote.go)
FetchActor— HTTP GET withAccept: application/activity+json, extracts inbox URL and public key PEM, stores inRemoteActortable to avoid repeated fetchesDeliverActivity— marshals activity JSON, signs the request, POSTs to recipient inbox with 15-second timeout
Added — Database Models (migration 014_federation)
FederationActivity— append-only log of all inbound and outbound activities:ActorAPID,Type,ObjectJSON,Direction(inbound/outbound),RemoteActor,PublishedRemoteActor— cache for remote actor documents:APID(unique),InboxURL,PublicKey,FetchedAt
0.8.0 — 2026-05-12
Phase 3E complete. Prometheus metrics, structured health checks, and per-repo operational health are operational.
Added — Prometheus Metrics (internal/observability/)
GET /metrics— Prometheus text format endpoint (standard root-level path for k8s/Prometheus scraping)GET /health— upgraded from static{"status":"ok"}to a structured liveness response:{"status":"healthy","checks":{"database":"ok","nats":"ok"},"version":"0.8.0"}Returns HTTP 503 when any dependency is degradedinternal/observability/metrics.go— metric definitions:forgebucket_http_requests_total{method,path,status}— counterforgebucket_http_request_duration_seconds{method,path}— histogram (Prometheus default buckets)forgebucket_pipeline_runs_total{status}— counter (succeeded/failed/cancelled), pre-initialized to 0forgebucket_deployments_total{status}— counter (pending/success/failure/cancelled), pre-initialized to 0forgebucket_active_pipeline_runs— gauge (in-flight runs)
internal/observability/health.go—Check(db, bus)pings PostgreSQL and callsbus.Healthy()- HTTP instrumentation middleware inserted after
Recoverer, beforeCORS— records every request - Path normalization prevents label cardinality explosion:
/repos/alice/myrepo/runs/42→/api/v1/repos/:owner/:repo/runs/:id - NATS metric watcher subscribes to
pipeline.>anddeployment.>and increments counters
Added — Per-Repo Operational Health (GET /api/v1/repos/{owner}/{repo}/health)
- Returns a JSON summary for the repo page operational header:
ciPassRate7d— fraction of pipeline runs that succeeded in the last 7 daystotalRuns7d— total run count in the last 7 dayslatestRun— most recentPipelineRunrecordlatestDeployments— one entry per environment showing latest deploy (envName, status, sha, finishedAt)openDriftCount— GitOpsConfigs indriftedstateopenPRCount— open pull request count
Added — EventBus Healthy() bool
- Added to the
EventBusinterface;NATSBusreturnsnc.IsConnected();NoOpBusreturnstrue
Changed — Middleware chain
observability.Middleware()added betweenRecovererandCORS(applies to all requests including/healthand/metrics)
0.7.0 — 2026-05-12
Phase 3D complete. Git is now the source of truth for environment deployment state.
Added — GitOps Controller (internal/domain/gitops/)
controller.go— starts as a background goroutine; subscribes topush.received,deployment.succeeded,deployment.failed; runs a periodic reconciliation ticker (interval configurable viaGITOPS_RECONCILE_INTERVAL); recovers stalesyncingconfigs todriftedon startupdrift.go—CheckDriftcallsgit rev-parsevia the existing git domain wrapper;handlePushqueries all GitOpsConfigs matching the pushed branch and evaluates drift;periodicCheckiterates configs whoseSyncIntervalhas elapsed; publishesenvironment.drift_detectedwhen drift is foundreconciler.go—TriggerSynccreates aDeploymentrecord and publishesdeployment.started(same lifecycle path as manual deployments,TriggeredBy="gitops");handleDeploymentSucceededresolves open drift events and marks configsyncedfor both GitOps and manual deployments;handleDeploymentFailedreverts todrifted
Added — GitOps HTTP API (internal/api/handlers/gitops.go)
All routes live under /api/v1/repos/{owner}/{repo}/environments/{envName}/gitops/:
GET /gitops— current GitOpsConfig or 404 if not configuredPUT /gitops— idempotent upsert (branch, autoSync, syncInterval)DELETE /gitops— remove config without deleting deploymentsPOST /gitops/sync— manual reconciliation trigger; creates deployment recordGET /gitops/drift— current sync status: syncStatus, desiredSha, actualSha, isDriftedGET /gitops/drift/history— paginated drift event log (newest first)POST /gitops/drift/{driftID}/acknowledge— acknowledge without syncing
Added — Database Models (migration 013_gitops)
GitOpsConfig— links environment to a branch; tracksDesiredSHA,ActualSHA,SyncStatus(unknown/synced/drifted/syncing),AutoSync,SyncInterval,LastCheckedAtGitOpsDriftEvent— append-only drift record:DesiredSHA,ActualSHA,SyncStatus(drifted/synced/acknowledged),DetectedAt,ResolvedAt
Added — Supporting Changes
git.RevParse(repoPath, ref)— new function ininternal/domain/git/binary.goused byCheckDriftto resolve branch HEAD SHAevents.DeploymentEvent+events.DriftEventtypes added tointernal/events/types.goEnvironmentHandler.publishDeployEventupdated to use sharedevents.DeploymentEventso the GitOps controller can unmarshal deployment lifecycle events correctlyGITOPS_RECONCILE_INTERVALenv var (default300s);0disables the periodic tickerArtifactRootconfig field +ARTIFACT_ROOTenv var
0.6.0 — 2026-05-12
Phase 3C complete. Multi-tenant workspaces and a full secret management hierarchy operational.
Added — Workspaces
Workspacemodel (migration011): globally unique handle, display name, description, avatarUrlWorkspaceMembermodel: owner/admin/member roles per workspace- Repository
workspace_idcolumn (optional; null = personal repo) - Full workspace CRUD API:
GET/POST /api/v1/workspaces,GET/PATCH/DELETE /api/v1/workspaces/{handle} - Workspace member management: list, add, update role, remove
GET /api/v1/workspaces/{handle}/repos— repos in workspace- Workspace frontend: WorkspacesPage, WorkspacePage, workspace switcher in sidebar header
- Workspace owner selector in repo create flow
Added — Secret Management (internal/api/handlers/secret.go)
Secretmodel (migration012):Scope(global/workspace/repo/env),ScopeID,Name,EncryptedValue(AES-256-GCM, never returned by API)- Unique constraint on (scope, scope_id, name)
- CRUD at all scope levels:
GET/POST/DELETE /api/v1/admin/secrets(global, admin-only)GET/POST/DELETE /api/v1/workspaces/{handle}/secrets(workspace-scoped)GET/POST/DELETE /api/v1/repos/{owner}/{repo}/secrets(repo-scoped)GET/POST/DELETE /api/v1/repos/{owner}/{repo}/environments/{envName}/secrets(env-scoped)
ResolveSecretsForRun(db, repoID, workspaceID, envID, sessionSecret)— hierarchy resolution for CI executor: Env > Repo > Workspace > Global- CI executor updated to inject resolved secrets as Docker
--envflags - RepoSecretsPage — write-only UI, values never displayed after creation
- Sidebar "Secrets" nav item in repo context
0.5.0 — 2026-05-11
Phases 3A and 3B complete. Environments, deployments, and the operational timeline are operational.
Added — Environments + Deployments (Phase 3A)
Environmentmodel (migration010): repoId, name, URL, protectionRules (JSON)Deploymentmodel: envId, repoId, sha, ref, status lifecycle (pending → in_progress → success/failure/cancelled), triggeredBy, description, runId link- CRUD API for environments:
GET/POST /environments,GET/PATCH/DELETE /environments/{envName} - Deployment API:
GET/POST /environments/{envName}/deployments,PATCH /environments/{envName}/deployments/{id}/status - NATS events published on status transitions:
deployment.started,deployment.succeeded,deployment.failed EnvironmentsPage— environment cards each showing latest deployment status, SHA, actor, and time since deploy; deployment history per env- Sidebar "Environments" nav item in repo context
- Repo page deployment status badges (latest deploy per env at a glance)
Added — Unified Operational Timeline (Phase 3B)
GET /api/v1/repos/{owner}/{repo}/timeline— merged chronological feed of commits, pipeline runs, and deployments; default 60 events, max 200RepoTimelinePageat/repos/:owner/:repo/timeline— vertical event feed with type filter tabs (all / commits / runs / deployments)- Sidebar "Timeline" nav item between Environments and Settings
- Answers "what changed before things broke?" without navigating between separate pages
0.4.0 — 2026-05-11
Phase 2C complete. CI results are legible in the UI; the dashboard is an operational command center.
Added — Pipeline Visualization
PipelinesPage— cross-repo pipeline runs feed with status filter tabs (all / running / failed / succeeded)RepoPipelinesPage— repo-scoped runs list at/repos/:owner/:repo/pipelinesPipelineRunPage— run detail with topological DAG visualization using realPipelineJob[]+needsgraph; step log viewer (collapsible per step, ANSI color, auto-scroll with lock toggle)PipelineWaterfall— rewritten to accept live job data instead of static mock stagesGET /api/v1/pipelines/runs— cross-repo recent runs for the dashboard
Added — Dashboard CI Command Center
- Dashboard CI widget replaced "coming soon" with live recent pipeline runs
- Dashboard
recentRuns[]field added to the/api/v1/dashboardresponse
Added — Command Palette Wiring
- Pipeline run results surfaced in command palette results
- "Pipelines" quick-nav action
0.3.0 — 2026-05-11
Phase 2B complete. Full CI/CD execution backend operational.
Added — CI Orchestrator (internal/domain/ci/)
- DAG-based pipeline orchestrator (
orchestrator.go): subscribes to NATSpush.received, parses.forgebucket/workflows/*.yml, createsPipelineRun/Job/Steprecords, advances DAG onjob.completed/failed, recovers stale runs on startup - Docker executor (
executor.go): steps run in isolated containers (docker run --rm), logs stream to DB and NATS viapipeline.log, workspace extracted viagit archive - Runner manager (
runner_manager.go): semaphore-limited (default 4 concurrent), subscribes tojob.queued, skips gracefully if Docker is unavailable - DAG engine (
dag.go):TopoSort,ReadyJobs - Workflow parser (
parser.go):.forgebucket/workflows/*.ymlfrom git ref,MatchesPushTriggerwith glob branch patterns;StringOrSliceYAML unmarshaler
Added — CI API Handlers
GET /api/v1/repos/:owner/:repo/pipelines— pipeline definitionsGET /api/v1/repos/:owner/:repo/runs— pipeline runs (newest first)GET /api/v1/repos/:owner/:repo/runs/:runID— run detail with job + step treePOST /api/v1/repos/:owner/:repo/runs/:runID/cancelPOST /api/v1/repos/:owner/:repo/runs/:runID/jobs/:jobID/retryGET /api/v1/repos/:owner/:repo/runs/:runID/jobs/:jobID/logs— step log chunksGET/POST /api/v1/repos/:owner/:repo/runs/:runID/artifactsGET /api/v1/repos/:owner/:repo/artifacts/:artifactID/download— path-traversal guardedGET/POST /api/v1/admin/runners— runner list + registration (admin-only, bcrypt token)
Added — Database Models (migration 009_ci)
Pipeline,PipelineRun,PipelineJob,PipelineStep,PipelineStepLogRunner(name, labels, status, tokenHash, lastSeenAt)Artifact(runId, repoId, name, storagePath, size, contentType)
Changed — Git HTTP handler
parseAndCheckBodyreplacescheckProtectionsFromBody— now also returns parsedrefUpdatestructs for publishingpush.receivedafter each successful receive-pack
0.2.0 — 2026-05-11
Phase 2A complete. Real-time event infrastructure and audit log operational.
Added — NATS Event Bus (internal/events/)
EventBusinterface:Publish,Subscribe,CloseNATSBus: NATS-backed with auto-reconnect;NoOpBusfallback whenNATS_URLunsetNew(url)factory: returnsNATSBusorNoOpBus- 40+ event subjects in
subjects.gocovering repo, push, PR, issue, pipeline, job, deployment, environment, and audit namespaces
Added — WebSocket Hub
GET /ws— NATS wildcard subscription (>) fans all events to connected clients as JSON{ subject, payload }envelope format- Goroutine per client with buffered send channel (64 events); slow clients drop events
Added — Audit Log (migration 008_audit_log)
AuditLogmodel: actorId, actorName, method, path, statusCode, ipAddress, userAgent- Middleware records every POST/PUT/PATCH/DELETE in the protected route group
- Writes DB row + publishes
audit.eventasynchronously (never blocks the response) GET /api/v1/audit— paginated, filterable by actor/method/since (admin-only)
0.1.0 — 2026-05-11
Initial development milestone. Core Git hosting, collaboration, and frontend SPA functional.
Added — Authentication & Security
- User registration and login with secure session cookies
- CSRF protection via double-submit cookie pattern (
X-CSRF-Token) - SSH key management per user
- OIDC / OAuth2 optional integration
- Scoped access tokens with optional expiration
- Repository deploy keys (read-only or read-write)
- ENV-driven config with fail-fast on missing secrets
Added — Git Hosting
- Smart HTTP transport (clone, push, pull over HTTP)
- AGit protocol (
refs/for/push for instant PR creation) - Branch management, commit log, diff viewing
- Git LFS per-repository (configurable file size limits)
- Branch protection rules (force-push blocking)
- Repository visibility (public / private)
Added — Collaboration
- Pull requests (open / merged / closed) with author tracking
- Issues (open / closed)
- Reviewer assignment (default reviewer per repo, per-PR overrides)
- Merge strategy selection per repository (merge / squash / rebase)
- Branching model configuration (feature / bugfix / release / hotfix prefixes)
- PR default description templates + excluded-files configuration
- Webhook system with event filtering (push, pull_request, issue)
- Repository member RBAC (read / write / admin)
Added — Frontend SPA
- React 18 + TypeScript + Vite, embedded into Go binary via
//go:embed - 20 route-level pages covering auth, dashboard, repos, code, PRs, issues, and settings
- Triple-state sidebar: expanded (320px) / collapsed (56px) / mobile bottom bar
- Mobile-first responsive design (375px → 1440px)
- DiffViewer (side-by-side + unified), MobileComment (bottom-sheet), TreeBrowser
Added — Design System
- Custom semantic token palette in
frontend/src/ui/tokens.ts - Full dark/light mode via Tailwind CSS v4
@variant dark - 8px grid system; 44px minimum touch targets (WCAG 2.5.5)
- System font stack (Segoe UI, Roboto, sans-serif)
Added — Infrastructure
- PostgreSQL + XORM with migrations 001–007
- ActivityPub actor data model (FederationActor) — data layer only
- Docker Compose for local PostgreSQL + NATS
- Makefile: dev, build, migrate, test, lint, docker-up