Annotated tag creation from the UI (follow-up to #253) #263
Labels
No labels
actions
bug
cli
duplicate
enhancement
fork
forum
git storage
help wanted
insights
invalid
issues
moderation
oidc
ownership transfer
packages
pr/merge protection rules
projects
pull requests
question
releases
sponsorships
tags
webhooks
wiki
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
crueber/walhub#263
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Follow-up to #253 (PR #262 shipped lightweight-only v1: a non-empty
messageon POST/{o}/{r}/api/tagsis rejected 422, never silently downgraded).Server-side annotated tags need a tag OBJECT in the object store (type/tagger/message), which no WAL entry kind carries today (proto is append-only/frozen). Options from #253: (a) extend the WAL with a tag-object entry kind (schema revision), or (b) construct the tag object server-side via the git binary and publish it as a PUSH-shaped entry.
Acceptance: POST with
messagewrites the tag object, records NewPeeled, shows tagger/message in git; lightweight path unchanged; events identical.Design ruling, #263 (annotated tag creation from the UI): rule (b) — git-binary-constructed tag object published as a PUSH-shaped entry. Reject (a).
Verdict
Implement (b): construct the tag object server-side with the
gitbinary, pack the single object through the existing ingest path, and publish it as anEntryKindPushentry whoseTxncarries therefs/tags/<name>create withNewPeeledset. No new WAL entry kind, no proto change, no coreinternal/walchange.Winner mechanics (b), step by step
All git spawns run in the repo git-dir (
GIT_DIR=<repo>,GIT_TERMINAL_PROMPT=0, bounded pool + ctx timeout, 8 KiB stderr discipline perdocs/go/04_git.md§2). Two new pinned argv (both require a04_git.md"Decisions & deviations" amendment in the implementing change, law 2 + law 12):git mktag, tag content on stdin.mktag— verified present in git 2.53, the floor version — applies strict fsck to the tag body, writes the loose object, and prints the tag oid. It is chosen overgit hash-object -t tag -w --stdinprecisely becausehash-objectperforms no fsck: it would accept a malformed tagger line/date that later fails fsck on fetch or breaksgit show. Canonical stdin bytes, server-rendered (LF, trailing newline):<oid>\n | git pack-objects --stdout(plain oid list on stdin, no--revs), capturing pack bytes from stdout. One object, no deltas; flags minimal.Layer.Ingestpath (git index-pack --stdin --keep --rev-index --threads=0 [--fsck-objects],thin=false— the pack is complete), yielding thePreparedPack(checksum + local pack/idx paths + sizes + count=1). Move order idx→rev→pack and zero-object rules are unchanged.Publish(PublishRequest{Pack: prepared, Txn: {Updates: [{Name: "refs/tags/<name>", OldOid: zero, NewOid: <tag-oid>, NewPeeled: <commit-sha>}]}, Meta: {principal, request_id, ...}}).publish.gomapsPack != niltoEntryKindPushand CASes the manifest; the bucket ACK (pack PUTs + log slot + manifest CAS) precedes the HTTP 201 (law 4 — never ACK before the bucket ACKs). The loose object from step 1 is serving-copy-local only and is not the replication unit; the pack PUT is what carries the object to every replica, which is why a REF_UPDATE-only publish would be a correctness hole (replicas materializing from packs alone would advertise a ref whose object they lack).Txncreate is CAS create (old-oid zero → present tag fails verify → 409, same as the #253 lightweight path, race-safe).NewPeeledcarries the peeled commit sha, so snapshots/advertisements render the^<peeled>continuation andpeeled:lines with zero new code — the field and its readers already exist (proto.RefUpdate.NewPeeled,Ref.Peeled). Lightweight path unchanged (REF_UPDATE, noPack, noNewPeeled).commitLocalalready folds txns for both PUSH and REF_UPDATE — but the implementing change must add a test proving a PUSH-shapedrefs/tags/*create emits the identical tag event as a REF_UPDATE create (acceptance criterion, not assumed).Failure semantics:
mktagnon-zero → 400/422, nothing published (malformed tagger/message never reaches the store);pack-objects/ingest failure → 5xx, nothing published; CAS conflict on existing tag → 409 via the verify step; CAS 412-restart handled by the existing ladder (pack PUTs are create-if-absent, safe to replay). No orphan risk beyond what §5.4 already covers.### Concurrencynote for the implementer: no new locks; per-repo serialization comes from the existing publisher single-flight + ingest lock (law 3, lock order ingest → wal sync preserved). The three spawns are sequential within the request (dependent), each pool-bounded with request-ctx cancellation.Loser (a), with rationale
(a) — a new
EntryKind(e.g. 6) carrying the tag object — is rejected:walgit.v1wire compat with the Rust implementation is byte-pinned by golden fixtures. An append-only enum add is legal proto, but every reader (replay, remote reader, checkpoint fold, compaction, events, Rust side) must learn the kind, and the compat matrix doubles for a payload the store already carries as opaque pack bytes. Schema revision for a data-plane blob is disproportionate.internal/store/proto+internal/wal); (b) touches only the feature package (internal/tags) plus two pinned argv — corePublishalready acceptsPack + Txn, proven bycommitLocalfolding both PUSH and REF_UPDATE txns.Ruled sub-questions
Principalcarries onlyName(no email), so the server synthesizes the tagger line as<sanitized-principal-name> <<sanitized-name>@walhub.local>with server wall-clock time + local tz offset. Sanitize: strip<,>,\n,\r; empty-after-sanitize → 400. Rationale: the tag must attribute the human who clicked (auditability —Meta.principalrecords the same name), while the fixed domain marks it as server-minted rather than claiming a verified external email. Anonymous → 401 before any of this (write gate, unchanged).\n.server.max_push_bytes. Rationale: tag messages are annotations, not blobs; the cap keeps the UI path off the bulk-bytes budget while leaving generous headroom (Linux kernel release notes fit in single KiBs).Annotated tags implemented per the ruling (b) in PR #267 (branch fix/issue-263, ready for review — not merged). mktag → pack-objects --stdout → Layer.Ingest → PUSH publish with Txn + NewPeeled; no new WAL kind, no proto change. Tagger synthesis + sanitization, 64 KiB cap, 400/5xx/409 failure map as ruled. E2E proves the tag object + peel survive a wiped-cache restart; events parity proven PUSH-vs-REF_UPDATE. internal/tags at 97.9% coverage, -race green, 528/528 node tests.
Review of PR #267 (fix/issue-263, commit
bdad949+ review nit892ac90), verified in scratch worktree against ruling (b) on issue #263:RULING COMPLIANCE — all elements verified in code, not assumed:
TEST RESULTS (scratch worktree /tmp/pr267):
FIX APPLIED: stale tagWire comment ('the created lightweight tag') now covers annotated responses; pushed as
892ac90to origin/fix/issue-263, tags tests re-run green.MERGE RECOMMENDATION: ready to merge.
Fixed by PR #267 incl. review comment fix (ruling (b) verified element-by-element; 97.9%/98.8% coverage), merged. Closing.