Address data-safety review of the server-side packaging hook:
- DATA LOSS (HIGH): tag generated zips with UploaderID = user_model.ActionsUserID
(-2), a built-in bot sentinel that cannot collide with human uploads (which
always use a positive doer ID). Only attachments carrying this marker are ever
deleted, so a user-uploaded asset sharing a generated name is never destroyed.
- CONCURRENCY (HIGH): serialize the per-release delete+attach sequence with
globallock.LockAndDo, keyed release_packaging_<id>, matching the forge's
existing working-lock convention. Attachments are re-read inside the lock.
- DELETE-THEN-FAIL (LOW): reorder to attach-new-then-delete-old — the fresh
archive is stored under the canonical name first and stale generated copies
are removed only on success, so a failed regeneration never nets asset loss.
- FILENAME (LOW): sanitize the tag segment (replace path separators) so a tag
like release/1.0 yields a clean asset name.
- ENTRY_POINT (LOW): normalizeEntryPoint trims a leading slash (git archive
rejects absolute paths) and treats a now-empty result as root/skip.
- CHECKSUM re-hash cost (MEDIUM): documented as a follow-up at the checksum
call sites in release.go.
The verified-correct io.Pipe streaming is unchanged.
Refs #809#812 EPIC #367
Authored-by: Moko Consulting
Add GenerateReleaseArtifacts in services/release/packaging.go, invoked from
CreateRelease and the UpdateRelease publish path (drafts skipped). It streams a
full-repository zip (<repo>-<tag>.zip) and, when repo metadata declares a
non-root entry_point, an entry_point subtree zip (<repo>-<tag>-source.zip) via
gitrepo.CreateArchive through an io.Pipe into attachment_service.NewAttachment,
so archives are never buffered in memory. The helper is idempotent (existing
artifacts of the same name are replaced) and runs before GenerateReleaseChecksums
so each zip receives a .sha256 sidecar automatically.
Refs #809#812 EPIC #367
Authored-by: Moko Consulting