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, always204regardless of FIPS posture. The body is drained before responding soExpect: 100-continueclients (mvn/gradledefaults) receive their100 Continue.- No
maven_filesrows created, noupload_sessionsconsumed, 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/mavenpackage suite: 293 pass.
AI pre-review note: this MR was pushed at the author's direction without the usual
/validate-step→/review-branch→/validate-stepdecorrelated 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)