Release: org-wide governance series (#727) — dev → main #733
Reference in New Issue
Block a user
Delete Branch "dev"
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?
Promotes all of
devtomain— this has grown well beyond the original org-governance series. 49 commits · 62 files · +8496 / −252.What this releases
Org governance (#727; #728–#732, #750): layered org branch/tag protection (most-restrictive, fail-closed), push policy (pre-receive), repo defaults, email-domain membership, per-user (username/email) branch-protection allowlists + Actions-bot toggle. Contiguous migrations 362–367.
Critical boot-crash fixes (production-relevant):
RC environment + deploy infra (#751):
deploy-rc.yml(auto-deployrc→ RC env), tier-scoped vars,workflow_dispatch; dev-deploy CI fixes (#737, #740).Manifest / brand cleanup (#754): removed the deprecated
.mokogitea/manifest.xmlparser +/manifestAPI alias;mokoplatform→mokocli.Test / CI repair: integration-test compile fixes (#755, #736), CI needs-chain scheduler workaround (#756), OpenAPI/swagger for the org endpoints (#739).
Status
mainrequires no status checks (was never a hard blocker).devis now functional — the chronicTestsscheduler stall was root-caused (Gitea doesn't schedule the dependent 2nd job of aneeds-chain) and fixed in #756;Testsnow schedules + runs. Lint / Secret-Scan / Branch-Policy pass.tests/integrationcompiles (go vetexits 0).mainand triggers the production deploy pipeline. This is a deliberate production release — owner's call.Post-merge follow-ups (not blockers)
stable-122-gafe46361c7.https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
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_01Wsno14cxE49MstXFs9G5KTAdds a single per-org push policy that cascades to every repo of the org and is enforced in the pre-receive hook: - Branch/tag name conventions (glob) — a pushed ref name must match. Fail-closed. - Mandatory secret-scanning block-on-push — org can force secret blocking that a repo cannot disable (overrides the per-repo scanner config in the orchestrator). - Max pushed-file size — rejects a tip tree containing a blob over the limit. - Blocked file-path patterns — rejects pushes changing matching paths (reuses pull_service.CheckFileProtection). The two content checks (blocked paths, max size) FAIL OPEN on any error so a policy/parsing bug can never wedge all pushes; naming is fail-closed. - models/git/org_push_policy.go: OrgPushPolicy model + CRUD + matchers + GetOrgPushPolicyForRepo. Migration 364. - API: GET/PATCH/DELETE /orgs/{org}/push_policy (routers/api/v1/org/push_policy.go, DTOs in modules/structs/org_push_policy.go, wired in api.go). - Enforcement: routers/private/hook_pre_receive.go (branch: naming + blocked paths + max size; tag: naming) and services/security/orchestrator.go (secret mandate). Deferred: a repo-facing read-only view of the org push policy (it is an org-wide config, not per-repo overlay rules; readable via the API for now). Stacked on #729/#728 for migration ordering (this = 364). Swagger annotations omitted (can't regenerate without the toolchain). Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here. Hand-verified: gofmt (tabs, no blank-in-block), escape sequences in the ls-tree parser, imports used, migration contiguous (364), fail-open on content checks. Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KTAdds a single per-org repository-defaults config, applied to a repo when it is created in or transferred into the org via a notifier (services/org): - ForcePrivate — force new/transferred repos private (Repository.IsPrivate). - PR defaults (when ApplyPRDefaults) — allowed merge styles, default merge style, and auto-delete-branch-after-merge, written to the repo's pull-requests unit config via repo_service.UpdateRepositoryUnits. Best-effort: the notifier logs and swallows errors, so a defaults bug can never break repository creation or transfer. - models/git/org_repo_defaults.go: OrgRepoDefaults model + CRUD + migration 365. - API: GET/PATCH/DELETE /orgs/{org}/repo_defaults. - services/org/notifier.go: CreateRepository/TransferRepository -> apply defaults; registered from routers/init.go (org_service.Init()). Stacked on #730/#729/#728 for migration ordering (this = 365). Swagger omitted. Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here. Hand-verified: gofmt (tabs, no blank-in-block, struct/DTO alignment), imports used, no Init() collision in services/org, migration contiguous (365), notifier signatures match the Notifier interface. Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KTRestricts which email domains an organization's members may have. When a policy is configured, a user can only be added to the org (via any team) if their primary email matches one of the allowed domain globs. Enforced at the single membership choke point services/org.AddTeamMember, which every add path (API, web, group-sync) funnels through — so one check covers them all. On violation it returns a typed ErrEmailDomainNotAllowed; the API team-add handler maps it to 422. - models/git/org_email_domain.go: OrgEmailDomainPolicy model + EmailAllowed (domain glob match) + OrgEmailDomainAllowed + typed error + CRUD. Migration 366. - API: GET/PATCH/DELETE /orgs/{org}/email_domain_policy. - Enforcement in services/org/team.go; 422 mapping in routers/api/v1/org/team.go. An empty policy imposes no restriction. This is the one bounded piece of the "access/security" tier; org 2FA-required and IP allowlists were deliberately NOT built here — they are cross-cutting enforcement (auth gating / request middleware) that needs a compiler + tests, not a blind stacked PR. Stacked on #731/#730/#729/#728 for migration ordering (this = 366). Swagger omitted. Note: no Go toolchain available locally, so not compiled/gofmt'd/tested here. Hand-verified: gofmt (tabs, no blank-in-block), imports (git_model added to the api team handler, gci order), typed-error detection, migration contiguous (366). Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KTAnnotate the four previously undocumented org-governance API handlers (tag_protection, push_policy, repo_defaults, email_domain) with swagger:operation blocks, and register the swagger:response models the branch_protection operations already referenced. Register the org option DTOs in the parameterBodies hack so their definitions are emitted. Also fix pre-existing spec-generation blockers surfaced once the spec became regenerable: a stray comment glued to the repoUpdateManifest swagger block (broke YAML parsing), missing owner/repo path params on the manifest operations, a Manifest response registration, and missing definitions for EditAccessTokenOption, the IssueBulk* options, and the Issue{Priority,Status,Type}Def types. Regenerated v1_json.tmpl and v1_openapi3_json.tmpl; spec now validates cleanly against Swagger 2.0. Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KTValidation update (2026-07-05) — the caveats in the original description are now resolved
The description above listed two open items before merging to
main. Both are now closed:1. OpenAPI / swagger — ✅ done (#739, merged to dev). All five org-governance API groups are now fully annotated (14 operations) with registered
swagger:responsemodels, andtemplates/swagger/v1_json.tmpl+v1_openapi3_json.tmplwere regenerated. Also fixed pre-existing spec-generation blockers (a stray comment inmanifest.go, dangling$refs inoptions.go) that had left the spec un-regenerable.swagger validate→ valid vs Swagger 2.0.2. Definitive build/runtime validation — ✅ done (local + live dev deploy).
go build ./...clean andgofmt -lclean on the full integrated tree (onlytests/integration, pre-existing-broken, excluded). Also fixed a pre-existing fork-wide compile break whereapi.gocalled the renamedHasOrgOrUserVisible(#735).custom/deploy-dev.yml, run 33608 attempt 2): image built → container recreated → migrations 362–366 applied → health check + dev APIhealthzpassed. Getting there also required two deploy-workflow fixes: #737 (tag var-expansion) and #740 (container-name conflict on recreate).This branch now also carries README + CHANGELOG updates and the regenerated spec (diff grew to ~49 files as a result).
Remaining (non-blocking, tracked in #738): MCP tools for the new org endpoints (B), repo-facing read views for push-policy/repo-defaults (C), and a small race-hardening of the dev deploy (a remote
flock, sincecancel-in-progressdoesn't kill already-launched remote SSH commands — two rapid dev merges raced on attempt 1).Merging this cuts the stable org-governance release to
main.https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Code-review findings on the org-governance release: - Fail closed on org-rule lookup error: getFirstMatchProtectedBranchRule swallowed FindOrgBranchRuleForBranch errors (returned nil,nil), silently dropping the org floor and falling back to the repo rule on a transient DB error. Propagate the error so the org rule stays enforced. - Stop the org rule locking out deploy-key and Actions-bot pushes: OrgProtectedBranch is team-only, so mergeMostRestrictive was ANDing the repo's WhitelistDeployKeys / WhitelistActionsUser (and the force-push, delete and merge counterparts) against the org side's always-false zero value, blocking every deploy-key and Actions push in any org with a matching branch rule. Carry those org-unmanaged fields through from the repo rule unchanged. - Org push-policy max-file-size now inspects only the pushed delta (diff-tree + cat-file --batch-check) instead of the full tip tree via ls-tree, so a pre-existing oversized file can no longer permanently block unrelated pushes. New branches (no base commit) still scan the full tree. Dev deploy targeting: - deploy-dev.yml drove the dev container image via `sed` on the SHARED compose file, but the pattern matched the *prod* service line (container_name: mokogitea) — leaving the dev service pinned to a stale image (so every "green" deploy recreated old code) while corrupting the prod image pin. Drive the dev service image from ${MOKOGITEA_DEV_TAG} instead; the env-var only affects the dev service. Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KTThe branch-protection delete feature (#696/#728) added a second "repo.settings.event_delete" entry ("Branch Deletion") to locale_en-US.json, reusing the existing webhook-event key (value "Delete"). The old JSON decoder silently kept the last value; Go 1.26's jsonv2 decoder rejects duplicate object keys, so InitLocales fails ("duplicate object member name repo.settings.event_delete") and the server crash-loops at startup. Like the code-scanner regexp panic, this only surfaces on a fresh boot, which is why it shipped unnoticed. Give the branch-protection section header its own key "repo.settings.protect_branch_deletion" and point protected_branch.tmpl at it, so the webhook "Delete" label and the branch-protection "Branch Deletion" header both render correctly and the JSON has no duplicate. Verified: no duplicate keys remain in any options/locale/*.json. Claude-Session: https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KTValidation update #2 (2026-07-05) — dev now boots the real code end-to-end + this release un-breaks
mainThe first dev deploys were a false green: the workflow's
sedtargeted the wrong service in the shared compose file, somokogitea-devkept recreating from a stale June-23 image and never ran this code. Fixing the deploy targeting (#741 + host compose edits) made it finally deploy the current image — which then exposed a chain of latent startup crashes that CI/build/vet never catch because they only fire atinit()/boot:services/security/code_scanner.goused an RE2-incompatible negative lookahead(?!…)inregexp.MustCompile→ panic at package init (from #552). Fixed in #742 (merged to dev) + regression test.locale_en-US.jsonhad a duplicaterepo.settings.event_deletekey (from #696/#728) → Go 1.26 jsonv2 rejects duplicate keys →InitLocalesfatal. Fixed in #743 (merged to dev); scanned all tracked + non-tracked JSON, no others.Now verified genuinely healthy:
ORM engine initialization successful!(all migrations incl. 362–366), indexers up, zero panic/fatal, still running at timeout.mokogitea-devrunningstable-236-g3efbab985b-dev, status=running, health=healthy, restarts=0; external/api/v1/version→ 200 (new commitg3efbab985b),/api/healthz→ 200pass.Impact on this PR:
devnow also contains #742 + #743, so merging #733 carries those boot fixes tomainas part of the release. That matters becausemaincurrently has the same two crashes (via #552 and #696/#728) and is undeployable from a fresh build — production only survives on an older pre-#552 image. This release both ships org-governance and makesmainbootable again.Net: #733 is validated at the code, build, migration, and live-boot levels. Ready to merge when you are.
https://claude.ai/code/session_01Wsno14cxE49MstXFs9G5KT
Wiki reminder: docs are wiki-first -- if this PR changes behavior, usage, config, or standards, please update the repo wiki before/after merge. (non-blocking)