docs: select controlled SBOM source contract
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s

Assistant: codex
This commit is contained in:
codex 2026-08-22 23:57:53 +02:00
parent 6171607710
commit cb62de1b7e
3 changed files with 202 additions and 6 deletions

View file

@ -406,8 +406,8 @@
| task | CUST-WP-0063-T03 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
| task | CUST-WP-0063-T04 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
| task | CUST-WP-0063-T05 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
| task | CUST-WP-0064-T01 | progress | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
| task | CUST-WP-0064-T02 | wait | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
| task | CUST-WP-0064-T01 | done | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
| task | CUST-WP-0064-T02 | progress | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
| task | CUST-WP-0064-T03 | wait | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
| task | CUST-WP-0064-T04 | wait | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
| intake | CUST-IN-0001 | closed | green | intake-legacy-suggestions-migration.md |

View file

@ -0,0 +1,186 @@
# Controlled SBOM scan input contract v1
**Status:** selected for implementation
**Decision date:** 2026-08-22
**Coordination:** `CUST-WP-0064`
**Owners:** Repo Manager, SBOM Nexus, Activity Core, `rapp-sbom-nexus`
## Decision
Authoritative daily catch-up will scan a Forgejo source archive addressed by a
full, immutable Git commit SHA. Repo Manager projects repository identity and
an observed default-branch revision; SBOM Nexus validates, fetches, extracts,
scans, persists provenance, and removes the source; Activity Core freezes the
source reference with the already-bounded target set; `rapp-sbom-nexus`
provides only the runtime filesystem and network boundary.
The initial provider is the public Coulomb Forgejo organization. The fetch
path is equivalent to:
```text
forgejo:coulomb/<repo-slug>@<40-lowercase-hex-commit>
-> /coulomb/<repo-slug>/archive/<commit>.tar.gz
```
The runtime constructs the fetch URL from configured Forgejo base and the
validated identity. The ingest API does not accept an arbitrary URL. This
prevents the scan path from becoming an SSRF surface or a general downloader.
An anonymous full-SHA archive fetch was proved on 2026-08-22 for
`coulomb/sbom-nexus@b95fba9a9f489b70e2640b62a0b3317583be742d`. No source
credential is required for v1. In particular, the Forgejo administrator PAT
must not be reused. A private repository remains unsupported until a separate,
least-privilege read identity and custody lane is approved.
## Wire contract
Repo Manager's projection supplies:
```json
{
"repo_slug": "sbom-nexus",
"source_ref": {
"kind": "forgejo-archive-v1",
"repository": "coulomb/sbom-nexus",
"revision": "b95fba9a9f489b70e2640b62a0b3317583be742d",
"observed_ref": "refs/heads/main",
"observed_at": "2026-08-22T21:38:58Z"
}
}
```
`revision` is always a full 40-character lowercase Git SHA. `repository` must
match the canonical Forgejo remote owned by Repo Manager and, for v1, must be
exactly `coulomb/<repo_slug>`. Repo Manager resolves the default-branch head
from Forgejo and records when it was observed; workstation paths and local
working-tree state are never used as production source.
SBOM Nexus stores the source reference in its repository projection and
returns it with each selected entry from `GET /sbom/catch-up`. Activity Core
records the exact returned reference in Temporal history together with the
fixed, at-most-N target set. Its ingest call is:
```http
POST /sbom/<repo_slug>/ingest
Idempotency-Key: <stable per-workflow-run/per-repository key>
X-Activity-Core-Operation-ID: <same value>
Content-Type: application/json
{"source_ref": { ...the selected source reference... }}
```
Retries reuse the same repository, source reference, and operation key even if
the default branch advances. SBOM Nexus rejects a source identity/slug
mismatch and a reused operation key with a different request fingerprint.
Successful replay returns the original terminal response and never creates a
second snapshot. This is the mutation-boundary requirement tracked by
`CUST-IN-0013`.
## Fetch and extraction boundary
SBOM Nexus is the only component that fetches or scans source. For each request
it creates a fresh directory on a dedicated ephemeral volume, streams the
archive to disk, computes its SHA-256, safely extracts one top-level directory,
scans it, commits the snapshot and operation receipt transactionally, and
removes the request directory in a `finally` path.
The v1 limits are:
| Control | Limit |
| --- | ---: |
| Concurrent controlled-source scans per Nexus pod | 1 |
| Fetch timeout | 120 seconds |
| Scan timeout | 120 seconds |
| Compressed archive | 100 MiB |
| Expanded files | 512 MiB total |
| Archive members | 100,000 |
| Ephemeral volume | 768 MiB |
| Redirects | same configured Forgejo host only |
Extraction rejects absolute paths, `..` traversal, links, devices, FIFOs, and
members outside the single archive root. The implementation streams bytes and
does not load the complete archive into memory. The application container
keeps its read-only root filesystem; only the bounded source volume is
writable. Logs may contain repository, revision, byte counts, durations,
outcome, and operation-key hash, but never archive content or credential data.
The package permits egress only to the Forgejo HTTP workload on port 3000 and
cluster DNS needed to resolve it, in addition to the existing PostgreSQL
egress. The configured runtime base is the cluster-local Forgejo service; the
canonical provenance identity remains `forgejo:coulomb/<slug>@<revision>`.
## Snapshot provenance and outcomes
An authoritative snapshot records at least:
- repository slug and canonical Forgejo repository identity;
- immutable Git revision and observed branch metadata;
- downloaded archive SHA-256 and byte count;
- manifest-relative paths and their existing content SHA-256 values;
- operation id, scanner version, start/end time, and terminal outcome.
The archive does not contain `.git`, so the scanner receives the validated
revision explicitly instead of attempting to infer it from the extracted
directory.
Terminal outcomes remain explicit:
| Condition | Outcome |
| --- | --- |
| Valid source, supported evidence, clean parse | `ingested` |
| Valid source, no supported evidence | `no-manifest` |
| Supported evidence with parser errors | `ingest-error` |
| No eligible Forgejo source reference or revision cannot be fetched | `source-unavailable` |
| Identity, redirect, archive, or extraction policy violation | `source-rejected` |
`source-unavailable` and `source-rejected` are additive skip reasons. Activity
Core must accept and report them without selecting a replacement repository in
the same fire. Only `ingested` advances `last_success_at`; every committed
terminal outcome advances `last_attempt_at` and preserves queue fairness.
## Ownership
| Owner | Required change |
| --- | --- |
| Repo Manager | Normalize the canonical Forgejo repository, observe its default-branch full SHA, and project the structured source reference. Missing, private, non-Coulomb, or unresolvable repositories have no v1 source reference. |
| SBOM Nexus | Add source-reference projection, controlled fetch/extraction, explicit revision override, snapshot provenance, new skip reasons, and durable operation receipts. It remains the only snapshot writer. |
| Activity Core | Freeze `source_ref` with the original bounded target set, submit it on ingest, preserve the operation key across retries, and report the additive skip reasons. It must not fetch source itself. |
| `rapp-sbom-nexus` | Add the size-limited ephemeral volume, Forgejo/DNS egress, configuration and resource controls, and live cleanup/rollback checks. It owns no scan semantics. |
## Unsupported and failure behavior
A missing source reference is visible in catch-up and becomes one terminal
`source-unavailable` outcome when selected. HTTP not-found, an unavailable
pinned commit, or a private repository without an approved v1 access path is
also `source-unavailable`. Timeout, oversize content, unsafe archive members,
an off-host redirect, identity mismatch, or malformed source reference is
`source-rejected`. A transport ambiguity after a committed write is resolved
only by replaying the same operation key; Activity Core must not synthesize a
skip or move to another target.
## Rollout and rollback
Roll out dark behind `SBOM_NEXUS_CONTROLLED_SOURCE_ENABLED=false`, migrate the
projection/provenance/idempotency schema, enable the source path for an
attended one-repository canary, then enable it for the existing daily bounded
definition. No increase to `catch_up_limit` is part of this change.
Rollback order is:
1. disable the Activity Core daily schedule and allow or cancel the one
in-flight bounded run;
2. set controlled source disabled and roll back the Nexus/package image digest;
3. remove Forgejo/DNS egress and the ephemeral volume if reverting the package;
4. retain all snapshots, operation receipts, and additive database columns.
Rollback never deletes SBOM history and never re-enables the retired weekly
task-flood rule. Local Nexus CLI scans remain non-authoritative preview work;
workstation checkouts are not a production fallback.
## Acceptance evidence
The implementation proof must show one attended and one normally scheduled
fire with the original target bound, stable operation ids and revisions across
retry, at least one real `ingested` snapshot, archive and manifest provenance,
zero spawned tasks, empty transient-source storage after completion, and a
rollback control that disables new source fetches without erasing history.

View file

@ -44,7 +44,7 @@ the deployment package owns the runtime/network boundary.
```task
id: CUST-WP-0064-T01
status: progress
status: done
priority: high
state_hub_task_id: "02ac7278-8536-5ce8-9027-39345aab0539"
```
@ -58,20 +58,25 @@ behavior, cleanup, and the boundary between preview and authoritative ingest.
Done when the four owning repos have one reviewable contract and rollback; do
not enable source transfer from a prose-only assumption.
**Started (2026-08-22):** live verification confirmed the private Nexus pod
**Done (2026-08-22):** live verification confirmed the private Nexus pod
cannot reach projected workstation checkout paths and that queue fairness is
therefore advancing through `no-checkout` outcomes. The Custodian fixed the
non-negotiable boundary—revision-pinned input, no workstation mount, bounded
credentials/egress/time/size, provenance, cleanup, and rollback—and routed
owner participation to SBOM Nexus (`95c1b226`), Repo Manager (`075e21de`),
Activity Core (`7233d2d1`), and the package owner (`e0af24b1`). Selection of
the concrete transfer mechanism remains in progress.
the concrete transfer mechanism is recorded in
`docs/sbom-controlled-scan-input-contract-v1.md`: a public Coulomb Forgejo
archive addressed by a full commit SHA, fetched and safely scanned by Nexus
from a bounded ephemeral volume. A live full-SHA archive fetch succeeded; v1
therefore needs no source credential and explicitly forbids reuse of the
Forgejo administrator PAT.
## Implement the Nexus-owned authoritative scan path
```task
id: CUST-WP-0064-T02
status: wait
status: progress
priority: high
state_hub_task_id: "2029e525-0573-5fea-881c-d3a418b91c9d"
```
@ -81,6 +86,11 @@ consume the selected input, scan at a pinned revision, persist provenance, and
remove temporary source material. Preserve Nexus as the only snapshot writer
and enforce `CUST-IN-0013` operation idempotency on the mutation boundary.
**Started (2026-08-22):** the selected v1 contract fixes the application and
package changes, additive skip reasons, safety limits, durable replay rule,
and rollback sequence. Implementation handoffs now carry one shared contract
instead of separate mechanism proposals.
## Retarget bounded catch-up without widening it
```task