sbom-nexus/ProductRequirementsDocument.md
tegwick ba535e1f8f feat: prepare postgres sbom cutover
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a028f0-a42f-7582-89a8-ebaad7343834
2026-08-22 13:14:24 +02:00

260 lines
11 KiB
Markdown

# Product Requirements Document — SBOM Nexus
**Status:** initial extraction baseline
**Date:** 2026-08-22
**Primary coordination:** `CUST-WP-0062`
**Initial implementation workplan:** `SBOM-WP-0001`
## 1. Product summary
SBOM Nexus is a headless service that captures software-composition evidence,
normalizes it into immutable per-repository snapshots, reports licence and
coverage signals, and selects a bounded oldest-stale queue for recurring
catch-up. It replaces State Hub as the SBOM store and product API and supersedes
Repo Manager's interim in-process scanner as the durable product owner.
## 2. Background
The current State Hub implementation provides snapshot and entry tables,
lockfile parsers, manual ingest, latest-snapshot queries, licence grouping,
MCP tools, dashboard views, and a `last_sbom_at` repository projection. A later
retirement slice reproduced the source-derived scanner in Repo Manager.
The operational trigger for extraction is `CUST-WP-0062`: a weekly freshness
rule created 75 tasks on 2026-08-17 while 111 of 111 registered repositories
were stale or never scanned. The desired control loop evaluates the fleet once,
selects the N oldest eligible repositories, and attempts no more than N updates
per run. N defaults to 3.
## 3. Goals
- Establish SBOM Nexus as the single durable SBOM product owner.
- Preserve State Hub's existing snapshot history and compatibility surface.
- Reuse and improve the repository-derived parsers already proven in State Hub
and Repo Manager.
- Provide one efficient, deterministic oldest-N catch-up query.
- Record success, empty evidence, unavailable checkout, and ingest failure so a
permanently impossible repository cannot block the queue.
- Keep `last_sbom_at` projections truthful throughout cutover.
- Give Activity Core a bounded read and ingest contract that can replace the
disabled weekly task-flood rule.
## 4. Non-goals for the extraction release
- Vulnerability correlation or exploitability analysis.
- Full licence-policy adjudication or legal advice.
- Adding ecosystems beyond those already handled by the source implementation.
- Build-time signing, attestations, or admission control.
- A new human dashboard; existing views are retargeted during cutover.
- An unbounded background scanner owned by SBOM Nexus.
## 5. Users and jobs
| User | Job |
| --- | --- |
| Fleet operator | Find and update the oldest stale SBOMs without creating an unbounded workload |
| Repository maintainer | Scan a checkout, see parser errors and provenance, and retrieve the current snapshot |
| Activity Core | Fetch at most N ordered targets and record bounded ingest/skip outcomes |
| State Hub compatibility clients | Continue using the current `/sbom/*` read and ingest contract during migration |
| Compliance/security consumer | Query components, licence distribution, and direct production copyleft triage signals |
| Migration operator | Import historical snapshots while preserving ids, timestamps, sources, and repository association |
## 6. Functional requirements
### FR-1 Repository representation
The service shall maintain the minimum repository projection required for SBOM
operations: stable slug, active/in-scope flag, optional checkout path, last
attempt time, last successful ingest time, and latest outcome/source. Repo
Manager remains upstream authority for repository identity and paths.
### FR-2 Source detection and parsing
The extraction release shall detect sources recursively while excluding
dependency/build directories. It shall support:
- `uv.lock` and `requirements.txt` (Python);
- `package-lock.json` and `yarn.lock` (Node);
- `Cargo.lock` (Rust);
- `go.sum` with `go.mod` direct-dependency hints;
- `.terraform.lock.hcl`;
- `ansible/requirements.yml` and `.yaml` below an `ansible` directory;
- reviewed root `sbom-tools.yaml`.
Every detected source shall have a repository-relative path, SHA-256 digest,
entry count, and any parser error. A repository Git revision is captured when
available.
### FR-3 Immutable snapshots
Every manual ingest, checkout scan, skip, error, or historical import shall
create a new immutable snapshot. Entries belong to exactly one snapshot.
Previous snapshots remain queryable. Snapshot status and source distinguish at
least `ingested`, `no-checkout`, `no-manifest`, `ingest-error`, and `imported`.
### FR-4 Current inventory queries
The service shall return:
- all snapshots, optionally filtered by repository;
- a snapshot and its entries;
- entries from the latest snapshot per repository with existing State Hub
filters (`ecosystem`, `license_spdx`, `is_direct`, `is_dev`);
- the latest repository SBOM view;
- fleet and per-repository freshness metadata.
### FR-5 Licence report
The extraction release shall group latest-snapshot entries by SPDX licence
text, identify simple copyleft-family triage matches, and count/list direct
non-development matches. The API and documentation shall label this as a signal
rather than a complete SPDX expression or legal-policy evaluation.
### FR-6 Bounded catch-up
`GET /sbom/catch-up?limit=N` shall:
- default N to 3 and constrain it to 1..25;
- consider only active/in-scope repositories;
- use a configurable stale threshold, initially 30 days;
- order never-attempted repositories first, then oldest attempt, then slug for
deterministic ties;
- return no more than N repositories;
- include `stale_count`, `never_count`, `total_count`, and effective `limit`;
- return per target `repo_slug`, `last_sbom_at`, `sbom_age_days`, `has_sbom`,
and `checkout_available` when known.
### FR-7 Bounded ingest and skip
A repository ingest operation shall use the registered checkout unless an
explicit authorized path is supplied. It shall create one of these terminal
outcomes:
- `ingested`: supported sources parsed and snapshot stored;
- `no-checkout`: no reachable checkout;
- `no-manifest`: checkout exists but no supported source exists;
- `ingest-error`: one or more sources could not be parsed.
Every outcome advances the repository's last-attempt time. Only `ingested`
advances last-success time. This prevents a permanently unavailable repository
from remaining first forever without falsely claiming a successful SBOM.
### FR-8 Compatibility
During cutover, SBOM Nexus shall preserve the response intent of State Hub's:
- `POST /sbom/ingest/`;
- `GET /sbom/`;
- `GET /sbom/{repo_slug}`;
- `GET /sbom/snapshots/` and `/sbom/snapshots/{id}`;
- `GET /sbom/report/licences/`.
Differences required for provenance or correctness shall be additive and
documented. State Hub may proxy these routes until callers move.
### FR-9 Historical migration
An import tool shall read historical State Hub snapshots in chronological
order and recreate their repository association, timestamp, source, entries,
and legacy identifier provenance. Import shall be idempotent. Migration must be
verified before State Hub rows are retired.
### FR-10 Operator interfaces
The repository shall provide commands to run the API, scan a checkout locally,
run tests/lint, and import State Hub history. Configuration shall be supplied
through explicit environment variables and contain no secrets in source.
## 7. Data model
```text
Repository 1 ─── * Snapshot 1 ─── * Entry
└── source records/errors/provenance
```
- **Repository:** slug, active, checkout path, last attempt/success, last status.
- **Snapshot:** UUID, repository, timestamp, status/source, source revision,
entry count, legacy id, structured errors.
- **Entry:** package name/version, ecosystem, licence text, direct/dev flags,
source path.
SQLite remains supported for extraction tests and single-node development.
Production uses PostgreSQL through the same transactional store and managed
Alembic migrations. Production cutover still requires backup/restore evidence
and explicit retention settings.
## 8. API contract
| Method | Path | Purpose |
| --- | --- | --- |
| GET | `/state/health` | Liveness and store readiness |
| PUT | `/repositories/{slug}` | Upsert the minimum Repo Manager projection |
| GET | `/repositories/` | List repository SBOM status |
| GET | `/sbom/catch-up?limit=N` | Select bounded oldest-stale targets |
| POST | `/sbom/{slug}/ingest` | Scan registered checkout and record terminal outcome |
| POST | `/sbom/{slug}/skip` | Record an explicit bounded skip outcome |
| POST | `/sbom/ingest/` | State Hub-compatible entry ingest |
| GET | `/sbom/` | Latest entries with filters |
| GET | `/sbom/{slug}` | Latest repository view |
| GET | `/sbom/snapshots/` | Snapshot history |
| GET | `/sbom/snapshots/{id}` | Snapshot detail |
| GET | `/sbom/report/licences/` | Latest-snapshot licence report |
## 9. Quality and operational requirements
- Catch-up ordering and bounds are covered by deterministic tests.
- A recorded skip demonstrably advances queue position.
- Ingest is transactional: a snapshot and its entries appear together.
- Paths returned by APIs are repository-relative unless explicitly operator-only.
- Parser failure in one source is visible; it must not silently produce a
successful snapshot.
- The API must not follow paths supplied by an untrusted remote caller in the
production profile; registered checkout paths are operator-managed inputs.
- Structured logs and metrics shall distinguish attempts, successes, skips,
errors, duration, and entry counts before production cutover.
- Backups and a restore drill are required before historical authority moves.
## 10. Migration and rollout
1. Establish this repo, intent, PRD, inventory, package, and tests.
2. Implement the compatible API, scanner, persistence, and catch-up contract.
3. Register/synchronize repository projections from Repo Manager.
4. Import and reconcile State Hub history; compare repository/snapshot/entry
counts and sampled licence reports.
5. Put State Hub `/sbom/*` behind a proxy/strangler and retarget MCP/dashboard.
6. Enable Activity Core's bounded ingest side effect, then its disabled daily
definition; retain the weekly flood disabled.
7. Observe at least two successful production fires and verify at most N
terminal outcomes per fire.
8. Remove State Hub ownership only after compatibility and retention gates pass.
## 11. Acceptance criteria
- Existing parser fixtures and State Hub-compatible route behaviors pass in
SBOM Nexus.
- `limit=3` returns exactly the three oldest eligible stale repositories when
at least three exist, with never-attempted repositories first.
- Recording a no-checkout or no-manifest outcome removes that repository from
the head of the immediate queue.
- Historical State Hub snapshot and entry counts reconcile, with documented
treatment of any invalid legacy row.
- State Hub is no longer the permanent SBOM store.
- Activity Core performs no more than N ingests/skips per fire and spawns zero
per-repository catch-up tasks.
- `last_sbom_at` compatibility projections are derived from a documented Nexus
timestamp and remain truthful.
## 12. Deferred product decisions
- Canonical component identity and deduplication across ecosystems.
- Full SPDX expression parsing and policy ownership.
- SPDX/CycloneDX import/export profiles and conformance level.
- Vulnerability, VEX, attestation, signing, and deployment correlation.
- Artifact Store integration and snapshot retention policy.
- Multi-tenant authorization via Tenant Engine and Flex Auth.
- Whether `last_sbom_at` compatibility maps to last attempt or last successful
inventory; Nexus retains both so consumers can migrate to precise fields.