feat(maven): upload-flow sidecar PUTs (S10 Step 7)

What

Implements S10 Maven hosted — Step 7: Upload flow, sidecars (spec Upload Step 4a + 4b). Fills the digest-sidecar PUT branch of the Maven handler:

  • PUT .sha1 / .sha256 / .sha512 — validate the body against the parent file's stored digest column (uniform algorithm; only expected hex length + column differ). Match → 204 No Content (no row written); mismatch → 409 checksum_mismatch; body not the expected hex (wrong length / non-hex / multiple lines / > 256 B) → 400 checksum_invalid_format; parent missing or soft-deleted at any level → 404.
  • PUT .md5 — accept-and-discard, always 204 regardless of FIPS posture. The body is drained before responding so Expect: 100-continue clients (mvn/gradle defaults) receive their 100 Continue.
  • No maven_files rows created, no upload_sessions consumed, no storage object written on any sidecar PUT path.
  • 256-byte body cap, distinct from the primary-file max_artifact_size.

Reuses the route registered in Step 5, the DigestStore interface and its soft-delete-filtered lookups from Step 6, and the WriteProblem RFC 9457 envelope. No new exported surface; download.go and the rest of the package are untouched.

Checksum case-sensitivity — spec follow-up owed

The SHA-family comparison is case-insensitive, per Apache Maven MNG-2744 (checksum comparison has been case-insensitive since Maven 2.0.9 — uppercase and lowercase hex of the same value are equal). The implementation uses strings.EqualFold for the value compare and [0-9a-fA-F] for charset validation, so uppercase hex is valid format and never returns 400.

S10 Upload Step 4a's prose ("one line of lowercase hexadecimal characters") is descriptive of what mvn/gradle/sbt emit, not a case-sensitivity rule: the spec's enumerated reject list {wrong length, non-hex, multiple lines} omits case, and the monolith reference implementation (lib/api/maven_packages.rb) has no invalid-format path (non-match → 409). A follow-up spec-amendment MR should state the case-insensitivity explicitly so the prose and the reject list cannot be read as conflicting.

Test plan (run locally)

  • go test ./... — all packages pass.
  • go test -tags integration ./internal/format/maven/... — pass (testcontainers).
  • 14 TestSidecarPUT_* functions / 55 subtests added; internal/format/maven package suite: 293 pass.

AI pre-review note: this MR was pushed at the author's direction without the usual /validate-step/review-branch/validate-step decorrelated AI pre-review gate. Please account for that during human review.

Spec coverage (Step 7 owned/touched)

Full AC → test mapping (all 33 ACs) is in the test(maven): … commit body. Step 7's owned and touched rows:

# Criterion Tests
AC-12 SHA-family match → 204, no row/storage/session; case-insensitive (MNG-2744) TestSidecarPUT_SHAFamily_Match_204, ..._UppercaseHexMatch_204, ..._TrailingWhitespaceTolerated, TestSidecarPUT_SHA1_PackageMetadata_Match_204
AC-13 Mismatch → 409 checksum_mismatch (wrong-value) TestSidecarPUT_SHAFamily_Mismatch_409
AC-14 Parent missing → 404 TestSidecarPUT_SHAFamily_ParentMissing_404
AC-15 Not expected hex (wrong length / non-hex / multiline) → 400 checksum_invalid_format; case is not a format violation TestSidecarPUT_SHAFamily_InvalidFormat_400
AC-17 .md5 PUT → 204 regardless of FIPS posture / body; no row, no storage, no MD5 call TestSidecarPUT_MD5_Always204
AC-20 PUT to remote/virtual repo → 405 Allow: GET, HEAD (Step 5 kind gate, before any lookup) TestSidecarPUT_NonHostedRepository_405
AC-27 Soft-deleted parent at any level → 404 (re-verified on the PUT path; Step 6 owns AC-27) TestSidecarPUT_SHAFamily_ParentMissing_404
AC-30 RFC 9457 envelope on every non-success (409/404/400/405) assertSidecarProblem across the suite

Security considerations

Concern Tests
256-byte sidecar body cap (distinct from max_artifact_size) TestSidecarPUT_SHAFamily_OversizeBody_400, TestSidecarPUT_MD5_OversizeBody_400
Sidecar trust — SHA-family validated case-insensitively; .md5 no-op ..._Match_204, ..._UppercaseHexMatch_204, ..._Mismatch_409, _MD5_Always204
FIPS — .md5 PUT still 204 (Gradle compatibility); SHA validation identical _MD5_Always204 (FIPS rows), ..._FIPSIdentical
Expect: 100-continue body drained before response _MD5_Expect100Continue_DrainsThen204, _SHA1_Expect100Continue_ValidatesBody (100 Continue observed via httptrace)

Related to #21 (closed)

Edited by Moaz Khalifa

Merge request reports

Loading
Loading