Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,23 @@ the compatibility and migration notes before upgrading.
view (`GET /v1/supply-chain/repositories/{id}/component`). No route means no
outbound license traffic. Migration 034 adds the evidence, enrichment-job,
and assessment tables.
- Portfolio read APIs over the caller's authorized repositories: an overview
whose every count names its denominator, keyset-paginated unique
coordinates with ecosystem, search, license, and assessment filters,
bounded facets, a coordinate detail listing authorized occurrences, a CSV
export with provenance columns and formula-safe cells, and a snapshot
comparison that separates component, declared-license, and edge changes
from document metadata changes.
- Standards-based imports of SPDX 2.3 JSON and CycloneDX 1.6 JSON into
declared `import:<subject>:<label>` streams (`POST /v1/supply-chain/imports`),
with format detection from the document, explicit rejection of other
formats and versions, byte-preserving storage, uploader identity recorded
apart from the claimed producer, producer-asserted subject binding,
idempotency, per-repository quotas, and administrator-managed upload grants
(`PUT /v1/supply-chain/upload-grants`). A derived SPDX export
(`GET /v1/supply-chain/exports/{id}/derived.spdx.json`) names GraphNest as
creator, links the preserved original, and carries assessments as comments
only. Migration 035 adds imports and upload grants.

## [0.5.0] - 2026-09-18

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ What the inventory is and is not:
- A failed refresh (403, 404, rate limit, malformed or oversized document, outage) records a collection attempt and leaves the last successful snapshot in place; the status reports `collection: failed` alongside the retained inventory.
- Inventory eligibility is repository authorization alone. It works for repositories with no Zoekt index, no SCIP upload, and no graph enrichment, and inventory work never blocks lexical indexing.

SBOMs produced elsewhere (Syft, ORT, or any tool writing SPDX 2.3 JSON or CycloneDX 1.6 JSON) can be imported into separate `import:<subject>:<label>` streams with `POST /v1/supply-chain/imports`; the uploader is recorded apart from the producer the document claims, and a derived SPDX export links back to the preserved original. Portfolio views (`/v1/supply-chain/overview`, `/components`, `/facets`, exports, comparison) aggregate only over the caller's authorized repositories and name every denominator.

Every read resolves the live principal's repository scope before any inventory row is touched; snapshot and job identifiers outside that scope are indistinguishable from missing ones. Manual refresh (`POST /v1/supply-chain/repositories/{id}/refresh`) only enqueues a bounded background job and requires administrator access. The published snapshot is also projected into the existing GitHub-sourced SCIP package mappings; manual mappings are never touched. See [Operations](docs/operations.md#dependencies--licenses-inventory) and [ADR-0017](docs/adr/0017-supply-chain-inventory.md).

## Durable mode
Expand Down
16 changes: 15 additions & 1 deletion cmd/graphnest-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import (
"github.com/balcsida/graphnest/internal/webhook"
"github.com/balcsida/graphnest/internal/webui"
"github.com/balcsida/graphnest/internal/zoekt"
"github.com/jackc/pgx/v5"
"github.com/modelcontextprotocol/go-sdk/mcp"
"golang.org/x/net/idna"
)
Expand Down Expand Up @@ -470,9 +471,22 @@ func newDurableRuntime(ctx context.Context, settings config.Config, logger *slog
License: store, EnrichmentEcosystems: registry.Ecosystems()}
supplyChainDone = startSupplyChain(loopCtx, settings.SupplyChain, store, githubClient, registry, metrics, logger)
portfolio := &supplychain.Portfolio{Store: store, Snapshots: store, Authorizer: authz.NewPostgres(store), Interval: settings.SupplyChain.Interval, MaxResults: settings.Limits.MaxResults}
importer := &supplychain.Importer{Store: store, Authorizer: authz.NewPostgres(store), MaxDocumentBytes: settings.SupplyChain.MaxDocumentBytes, Limits: supplychain.Limits{MaxComponents: settings.SupplyChain.MaxComponents}}
if len(registry.Ecosystems()) > 0 {
importer.Enricher = &license.Worker{Store: store, Registry: registry}
}
authorizer := authz.NewPostgres(store)
grants := &httpapi.UploadGrants{Set: store.SetSupplyChainUploadGrant, Resolve: func(ctx context.Context, principal authn.Principal, githubID int64) (int64, error) {
repo, err := authorizer.AuthorizedRepository(ctx, principal, githubID)
if errors.Is(err, pgx.ErrNoRows) {
return 0, supplychain.ErrNotFound
}
return repo.ID, err
}}
extras = append(extras, func(mux *http.ServeMux) {
httpapi.RegisterSupplyChain(mux, auth.requestAuth, supplyChainService, settings.Limits.MaxResults, settings.Limits.MaxResponseBytes)
httpapi.RegisterSupplyChainPortfolio(mux, auth.requestAuth, portfolio, settings.Limits.MaxResults, settings.Limits.MaxResponseBytes)
httpapi.RegisterSupplyChainPortfolio(mux, auth.requestAuth, portfolio, settings.Limits.MaxResults, settings.Limits.MaxResponseBytes, &httpapi.DerivedExport{Service: supplyChainService, PublicOrigin: auth.requestAuth.PublicOrigin})
httpapi.RegisterSupplyChainImports(mux, auth.requestAuth, importer, grants, settings.SupplyChain.MaxDocumentBytes, settings.Limits.MaxResponseBytes)
})
}
handler := newAPIHandler(settings, metrics, auth.requestAuth, searchService, repositoryService, scipService, graphService, graphQueries, webhookSecret, processor, adminService, durableReadiness{pool: pool, zoekt: backend}, auth.providers, auth.sessions, provisioning, scimService, auth.mcpOAuth, extras...)
Expand Down
Loading
Loading