feat(org): org-level tag protection, layered with per-repo protected tags (#727) #729

Merged
jmiller merged 1 commits from feat/org-tag-protection into dev 2026-07-05 04:33:02 +00:00
Owner

Part of the org-governance series (see #728). Adds org-level tag protection as a parallel to org-level branch protection.

⚠️ Stacked on #728 — this branch is based on fix/727-materialize-org-branch-protection so the migration numbering stays contiguous (org branch-delete = 362, org tag table = 363). Merge #728 first. Until then, this PR's diff also shows #728's commits; it self-cleans once #728 lands. The tag-protection commit itself is b31336d (12 files, +419).

What it does

An org tag rule is {NamePattern, AllowlistTeamIDs} (much simpler than branch rules — tags have no booleans). It cascades to every repo in the org and layers on top of the repo's own protected tags: a tag is controllable (push/delete) only if allowed at both levels (fail-closed AND).

How

  • Model models/git/org_protected_tag.go: OrgProtectedTag + CRUD + ToProtectedTag() (reuses the existing ProtectedTag name-matcher/allowlist) + IsUserAllowedToControlTagInRepo() which ANDs the repo decision with the org decision. Migration 363.
  • API /orgs/{org}/tag_protections (list/get/create/edit/delete) — routers/api/v1/org/tag_protection.go, DTOs in modules/structs/org_tag.go, wired in api.go beside branch_protections.
  • Enforcement — the single git push/delete hook (hook_pre_receive.go) and the two release paths (release.go create/delete) now call the layered helper. No per-site tag logic changed beyond swapping the function.
  • View — the repo Tag settings page lists inherited org tag rules read-only (pattern + allowed teams + lock marker).

Caveats

  • No Go toolchain locally → not compiled/gofmt'd/tested here. Hand-verified: gofmt (tabs, no blank-in-block, struct-field alignment), template nesting balances, every .Rule field exists on OrgProtectedTag, all locale keys defined, JSON valid, migration contiguous.
  • Swagger annotations omitted on the new handlers (I can't regenerate templates/swagger/v1_json.tmpl without the toolchain, and the existing org-branch handlers already reference undefined #/responses/...). Routes register fine; docs can be regenerated later.
  • CI must validate build + format + tests; a manual push/delete of a protected tag on an org repo is the key integration check.

https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT

Part of the org-governance series (see #728). Adds **org-level tag protection** as a parallel to org-level branch protection. > **⚠️ Stacked on #728** — this branch is based on `fix/727-materialize-org-branch-protection` so the migration numbering stays contiguous (org branch-delete = 362, org tag table = **363**). **Merge #728 first.** Until then, this PR's diff also shows #728's commits; it self-cleans once #728 lands. The tag-protection commit itself is `b31336d` (12 files, +419). ## What it does An org tag rule is `{NamePattern, AllowlistTeamIDs}` (much simpler than branch rules — tags have no booleans). It cascades to every repo in the org and **layers** on top of the repo's own protected tags: a tag is controllable (push/delete) only if allowed at **both** levels (fail-closed AND). ## How - **Model** `models/git/org_protected_tag.go`: `OrgProtectedTag` + CRUD + `ToProtectedTag()` (reuses the existing `ProtectedTag` name-matcher/allowlist) + `IsUserAllowedToControlTagInRepo()` which ANDs the repo decision with the org decision. Migration **363**. - **API** `/orgs/{org}/tag_protections` (list/get/create/edit/delete) — `routers/api/v1/org/tag_protection.go`, DTOs in `modules/structs/org_tag.go`, wired in `api.go` beside `branch_protections`. - **Enforcement** — the single git push/delete hook (`hook_pre_receive.go`) and the two release paths (`release.go` create/delete) now call the layered helper. No per-site tag logic changed beyond swapping the function. - **View** — the repo Tag settings page lists inherited org tag rules **read-only** (pattern + allowed teams + lock marker). ## Caveats - **No Go toolchain locally** → not compiled/`gofmt`'d/tested here. Hand-verified: gofmt (tabs, no blank-in-block, struct-field alignment), template nesting balances, every `.Rule` field exists on `OrgProtectedTag`, all locale keys defined, JSON valid, migration contiguous. - **Swagger annotations omitted** on the new handlers (I can't regenerate `templates/swagger/v1_json.tmpl` without the toolchain, and the existing org-branch handlers already reference undefined `#/responses/...`). Routes register fine; docs can be regenerated later. - CI must validate build + format + tests; a manual push/delete of a protected tag on an org repo is the key integration check. https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
jmiller changed target branch from main to dev 2026-07-05 03:58:20 +00:00
jmiller added 31 commits 2026-07-05 03:58:20 +00:00
release: code security scanner (#552)
Deploy MokoGitea / deploy (push) Failing after 5m9s
f8a91ed34e
Code security scanner with 22 OWASP pattern detection rules across 7 CWE categories (SQL injection, XSS, command injection, path traversal, insecure deserialization, hardcoded credentials, weak cryptography). Language-filtered scanning for Go, PHP, Python, JS/TS, Java, C#, Ruby.
fix: remove orphaned deploy-manual workflow [skip ci]
fix: support radio inputs in admin system config form
Universal: PR Check / Branch Policy (pull_request) Failing after 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Failing after 11s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 38s
Universal: Build & Release / Promote to RC (pull_request) Failing after 8s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 39s
Universal: PR Check / Secret Scan (pull_request) Successful in 43s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
982e45a56e
The system config form JS (config.ts) only mapped checkbox, text, textarea, and datetime-local elements. The fork landing_page.tmpl uses radio inputs for the Mode field, so fillFromSystemConfig() hit unsupportedElement() and threw, aborting all JS init on the admin settings page.

Add radio handling in both directions: fill checks the option whose value matches the config value; collect returns the checked option's value and skips/nulls unchecked radios so a group resolves to exactly one value. Adds a radio-group test case.
fix: preserve server-rendered radio default when config value is empty
Universal: PR Check / Branch Policy (pull_request) Failing after 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Failing after 11s
Generic: Project CI / Lint & Validate (pull_request) Successful in 37s
PR RC Release / Build RC Release (pull_request) Failing after 1m3s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 1m5s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 1m9s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
efb0433412
LandingPageType.Mode defaults to "" (Go zero value), and the template renders the home radio as checked for an empty Mode. The initial radio fill would evaluate home.checked = ("home" === "") = false, unchecking the default on a fresh install. Skip assignment when the config value is empty so the server-rendered selection is preserved. Adds a test for the empty-value case.
ci: allow fix/patch branches to target main and guard missing manifest
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 11s
Generic: Project CI / Lint & Validate (pull_request) Successful in 36s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Failing after 1m0s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 57s
Universal: PR Check / Secret Scan (pull_request) Successful in 57s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
b252e9569f
Branch policy in pr-check.yml only allowed fix/* and patch/* to target dev/rc, blocking fix/* PRs to main despite the documented policy. Allow fix/* -> main and patch/* -> main. Also guard the Detect platform step for a missing .mokogitea/manifest.xml (removed in favor of the metadata API) so it no longer aborts the Validate PR job under set -e.
fix: remove dangling mcp-mokogitea-api submodule gitlink
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 11s
Generic: Project CI / Lint & Validate (pull_request) Successful in 39s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 42s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Failing after 1m11s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m12s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
fc234bc911
The tree carried a gitlink at mcp-mokogitea-api (mode 160000) with no .gitmodules entry, so git submodule update --init --recursive failed with exit 128 at checkout, breaking every PR build/release job. mcp-mokogitea-api is a separate repo, not a submodule; remove the gitlink from the index (keeping the local working-tree clone) and gitignore the path so it can't be re-added.
ci: no-op PR RC Release when updates.xml is absent
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 11s
Generic: Project CI / Lint & Validate (pull_request) Successful in 33s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 49s
PR RC Release / Build RC Release (pull_request) Successful in 1m34s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m36s
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 55s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Successful in 8m57s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
aea4370845
The RC release workflow drives a Joomla-style updates.xml update stream. On a generic repo with no updates.xml, the Determine RC version step ran sed on a missing file and aborted under set -e (exit 2). Detect updates.xml presence and gate the update-stream steps (edit/create-release/commit) on it so the job succeeds and no-ops when there is nothing to package.
fix: render org teams list and make issue type editable (#720, #721)
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 14s
Generic: Project CI / Lint & Validate (pull_request) Successful in 57s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m15s
PR RC Release / Build RC Release (pull_request) Successful in 2m18s
Universal: PR Check / Secret Scan (pull_request) Successful in 2m19s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Failing after 1m10s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 9m20s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
0cc569aef6
#720: org Teams page wrote ctx.Data["OrgListTeams"] but the template iterates .Teams, so no teams rendered. Use the canonical Teams key (matches org/home.go). #721: issue type sidebar gated editing on a FieldEditFlags data key that no handler sets (always nil -> always read-only). Use HasIssuesOrPullsWritePermission like the priority field; the /custom-type endpoint is already protected by reqRepoIssuesOrPullsWriter.
fix(org): layer org-level branch protection with repo rules, most-restrictive wins (#727)
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Project CI / Lint & Validate (pull_request) Successful in 38s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 10s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m8s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 3m15s
Universal: PR Check / Secret Scan (pull_request) Successful in 3m5s
Generic: Project CI / Tests (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
24b3516c1d
Org-level branch protection was already consulted at the single enforcement
choke point `GetFirstMatchProtectedBranchRule`, but only as a FALLBACK: if any
repo-level rule matched the branch, the org rule was ignored entirely. That let
a repo define a looser rule for a pattern and effectively opt out of the org's
protection.

Make the choke point LAYER the two rules instead: when both an org rule and a
repo rule match a branch, return their most-restrictive (fail-closed)
combination, so the org rule is a mandatory floor a repo can only tighten.

- models/git/protected_branch_merge.go: mergeMostRestrictive + helpers. Allow
  flags AND'd; gate/require/block flags OR'd; RequiredApprovals max'd; required
  sets (status contexts, protected files) unioned; allow sets (whitelists,
  unprotected files) intersected. A disabled allowlist means "everyone", so it
  only constrains when enabled.
- models/git/protected_branch_list.go: GetFirstMatchProtectedBranchRule now
  fetches both the repo rule and the org rule and merges when both match;
  returns whichever exists when only one matches. Org lookup factored into
  getFirstMatchOrgProtectedBranchRule.

Supersedes the materialization approach previously proposed for this issue —
the org fallback already existed, so only this one function needed to change.

Fail-closed by design: any merge edge errs toward MORE protection (over-restrict)
rather than less, so it cannot open a hole.

Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here —
relying on CI to validate build, formatting, and tests.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
feat(org): show inherited org branch-protection rules in repo settings (#727)
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 13s
Generic: Project CI / Lint & Validate (pull_request) Successful in 42s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m18s
PR RC Release / Build RC Release (pull_request) Successful in 1m17s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: PR Check / Secret Scan (pull_request) Successful in 1m32s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
86bd8a2cad
The org "floor" is enforced implicitly at the choke point, so a repo admin
couldn't see which org-level rules apply to their repo. Surface them in the
repo's Branch Protection settings page (read-only), the way GitHub shows
organization rulesets in a repository.

- ProtectedBranchRules handler: when the owner is an org, load
  FindOrgProtectedBranchRules and expose them as OrgProtectedBranches.
- branches.tmpl: new read-only "Organization Branch Protection" section listing
  each org rule with an "Organization" badge, a lock/read-only marker, and
  compact indicators (required approvals, signed commits, status checks). No
  edit/delete controls — these are managed at the org level.
- en-US locale strings.

Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
feat(org): add branch-deletion protection + expandable inherited-rule view (#727)
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Project CI / Lint & Validate (pull_request) Successful in 39s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 1m3s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
PR RC Release / Build RC Release (pull_request) Successful in 1m8s
Generic: Project CI / Tests (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 12s
Universal: PR Check / Secret Scan (pull_request) Successful in 3m47s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
4b68853f08
Two related additions:

1. Branch deletion as an org-level ability. OrgProtectedBranch gained
   CanDelete / EnableDeleteAllowlist / DeleteAllowlistTeamIDs (migration 362),
   ToProtectedBranch maps them, and the API (create/edit/response DTOs +
   handlers) exposes enable_delete / enable_delete_allowlist /
   delete_allowlist_teams. The layering merge already combined delete fields, so
   org delete-protection now enforces once ToProtectedBranch populates them.

2. The repo Branch Protection view now renders each inherited org rule as an
   expandable detail (direct push, force-push, branch deletion, merge, required
   approvals, status checks, protected files) with team names resolved, instead
   of three headline badges. Still read-only.

Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here.
Verified by hand: struct-field gofmt alignment, template block nesting balances,
every .Rule field exists on OrgProtectedBranch, and all locale keys referenced
in the template are defined.

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
feat(org): org-level tag protection, layered with per-repo protected tags (#727)
Universal: Build & Release / Promote to RC (pull_request) Failing after 18s
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Generic: Project CI / Lint & Validate (pull_request) Successful in 39s
PR RC Release / Build RC Release (pull_request) Successful in 2m21s
Generic: Project CI / Tests (pull_request) Has been cancelled
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 11s
Universal: PR Check / Secret Scan (pull_request) Successful in 1m17s
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Branch Cleanup / Delete merged branch (pull_request) Successful in 1s
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
b31336d1fe
Adds org-level tag protection as a parallel to org-level branch protection.
An org tag rule is {NamePattern, AllowlistTeamIDs}; it cascades to every repo
in the org and layers on top of the repo's own protected tags — a tag is
controllable (push/delete) only if allowed at BOTH levels (fail-closed).

- models/git/org_protected_tag.go: OrgProtectedTag model + CRUD +
  ToProtectedTag() (reuses the ProtectedTag matcher/allowlist logic) +
  IsUserAllowedToControlTagInRepo() which ANDs the repo decision with the org
  decision. Migration 363.
- API: /orgs/{org}/tag_protections CRUD (routers/api/v1/org/tag_protection.go,
  DTOs in modules/structs/org_tag.go, wired in api.go).
- Enforcement: the git push/delete hook (hook_pre_receive.go) and the two
  release paths (release.go create/delete) now call the layered check, so no
  per-site tag logic changes beyond swapping the helper.
- View: the repo Tag settings page lists inherited org tag rules read-only.

Stacked on #728 (branch-protection PR) for migration ordering — merge #728
first. Swagger annotations omitted (can't regenerate the swagger JSON without
the toolchain); routes still register.

Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here.
Hand-verified: gofmt (tabs, no blank-in-block, struct alignment), template
nesting balances, all .Rule fields exist on OrgProtectedTag, all locale keys
defined, JSON valid, migration contiguous (363).

Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
jmiller merged commit 3a5c6a37cf into dev 2026-07-05 04:33:02 +00:00
jmiller deleted branch feat/org-tag-protection 2026-07-05 04:33:03 +00:00
Sign in to join this conversation.