#!/bin/bash

## Copyright (C) 2026 - 2026 ENCRYPTED SUPPORT LLC <adrelanos@whonix.org>
## See the file COPYING for copying conditions.

## AI-Assisted

## Single source of truth for GitHub policy bodies applied by the
## dm-github-* tools. Pure data - no functions, no API calls, no flow
## control. Sourcing is idempotent and side-effect-free.
##
## Render at the call site via:
##   "$(jq -n -- "${POLICY_X}")"
##
## The bodies are jq programs (relaxed syntax, unquoted keys), not
## JSON input. Pass them as the jq program argument ('-n -- <expr>'),
## not via stdin.

## Source via:
##   source /usr/libexec/developer-meta-files/github-policy-data.bsh

set -o errexit
set -o nounset
set -o pipefail
set -o errtrace
shopt -s inherit_errexit
shopt -s shift_verbose

## ============================================================================
## Shared bodies - used by both org and personal policy
## ============================================================================
##
## Each policy is a 4-tuple of readonly constants:
##   POLICY_X         body (jq program, unquoted keys)
##   POLICY_X_ENDPOINT  API path; '__ORG__', '__OWNER__', '__REPO__'
##                      placeholders substituted at the call site
##   POLICY_X_METHOD    HTTP verb
##   POLICY_X_LABEL     human-facing description for log lines

readonly POLICY_FORK_PR_APPROVAL='{
   approval_policy: "all_external_contributors"
}'
readonly POLICY_FORK_PR_APPROVAL_ENDPOINT_ORG='/orgs/__ORG__/actions/permissions/fork-pr-contributor-approval'
readonly POLICY_FORK_PR_APPROVAL_ENDPOINT_REPO='/repos/__OWNER__/__REPO__/actions/permissions/fork-pr-contributor-approval'
readonly POLICY_FORK_PR_APPROVAL_METHOD='PUT'
readonly POLICY_FORK_PR_APPROVAL_LABEL='fork-PR approval=all_external_contributors'

readonly POLICY_WORKFLOW_PERMS='{
   default_workflow_permissions: "read",
   can_approve_pull_request_reviews: false
}'
readonly POLICY_WORKFLOW_PERMS_ENDPOINT_ORG='/orgs/__ORG__/actions/permissions/workflow'
readonly POLICY_WORKFLOW_PERMS_ENDPOINT_REPO='/repos/__OWNER__/__REPO__/actions/permissions/workflow'
readonly POLICY_WORKFLOW_PERMS_METHOD='PUT'
readonly POLICY_WORKFLOW_PERMS_LABEL='workflow GITHUB_TOKEN read-only, no PR approval'

## CAUTION on rollout: workflows pinned to unverified third-party
## actions will start failing the moment this lands. Run --audit
## first; whitelist or replace any non-github-owned, non-verified
## action before applying.
readonly POLICY_ACTIONS_ALLOWLIST='{
   github_owned_allowed: true,
   verified_allowed: true,
   patterns_allowed: []
}'
readonly POLICY_ACTIONS_ALLOWLIST_ENDPOINT='/orgs/__ORG__/actions/permissions/selected-actions'
readonly POLICY_ACTIONS_ALLOWLIST_METHOD='PUT'
readonly POLICY_ACTIONS_ALLOWLIST_LABEL='selected-actions = github-owned + verified-creators'

## ============================================================================
## Org-policy bodies - used by dm-github-org-policy
## ============================================================================

## PAID PLAN ONLY (GitHub Advanced Security): the org-level
## code-security configuration body. POST
## /orgs/{org}/code-security/configurations rejects any GHAS-licensed
## feature (secret_scanning, secret_scanning_push_protection,
## code_scanning_default_setup) on a Free org with HTTP 400
## "GitHub advanced security must be enabled when any GHAS feature
## is set to enabled". Empirically tested 2026-05. On Free, the only
## fields the configuration API accepts are dependency_graph,
## dependabot_alerts, dependabot_security_updates,
## private_vulnerability_reporting - and the per-repo PUT endpoints
## already cover those without the org-wide attach dance. Re-enable
## this block (and the call sites in dm-github-org-policy under
## apply_code_security_config + step 5+6) only after the org upgrades
## to a plan bundling Advanced Security.
##
## 'name' is dynamic (set by caller via jq --arg) and omitted here.
# readonly POLICY_CODE_SECURITY='{
#    description: "Project-wide code security applied via dm-github-org-policy.",
#    enforcement: "enforced",
#    secret_scanning: "enabled",
#    secret_scanning_push_protection: "enabled",
#    dependabot_alerts: "enabled",
#    dependabot_security_updates: "enabled",
#    dependency_graph: "enabled",
#    code_scanning_default_setup: "disabled",
#    private_vulnerability_reporting: "disabled"
# }'

## Switches allowed_actions to "selected"; the per-action allowlist
## itself (POLICY_ACTIONS_ALLOWLIST above) is set by a follow-up call.
readonly POLICY_ACTIONS_SCOPE='{
   enabled_repositories: "all",
   allowed_actions: "selected"
}'
readonly POLICY_ACTIONS_SCOPE_ENDPOINT_ORG='/orgs/__ORG__/actions/permissions'
readonly POLICY_ACTIONS_SCOPE_METHOD='PUT'
readonly POLICY_ACTIONS_SCOPE_LABEL='actions enabled=all, allowed=selected'

## PAID PLAN ONLY: see POLICY_CODE_SECURITY note above.
# readonly POLICY_CODE_SECURITY_ATTACH='{
#    scope: "all"
# }'
# readonly POLICY_CODE_SECURITY_ATTACH_ENDPOINT_ORG='/orgs/__ORG__/code-security/configurations/__ID__/attach'
# readonly POLICY_CODE_SECURITY_ATTACH_METHOD='POST'
# readonly POLICY_CODE_SECURITY_ATTACH_LABEL='attach code-security config to all repos'

## PAID PLAN ONLY: see POLICY_CODE_SECURITY_ATTACH note above.
# readonly POLICY_CODE_SECURITY_DEFAULTS='{
#    default_for_new_repos: "all"
# }'
# readonly POLICY_CODE_SECURITY_DEFAULTS_ENDPOINT_ORG='/orgs/__ORG__/code-security/configurations/__ID__/defaults'
# readonly POLICY_CODE_SECURITY_DEFAULTS_METHOD='PUT'
# readonly POLICY_CODE_SECURITY_DEFAULTS_LABEL='set code-security config as default for new repos'

## members_allowed_repository_creation_type (deprecated),
## members_can_delete_repositories (UI-only), and
## two_factor_requirement_enabled (read-only on PATCH) are NOT in
## this body.
##
## members_can_create_internal_repositories is also NOT in this body.
## Internal repositories are Enterprise-only; on Free/Team/Pro orgs
## the field is server-side locked to false and GitHub rejects the
## entire PATCH with HTTP 422 ("Validation Failed: This organization
## does not support internal repositories") when it is mentioned at
## all - rolling back the other valid fields in the same request.
##
## deploy_keys_enabled_for_repositories=false: org-wide kill of the
## deploy-key feature. Deploy keys are a write-capable bypass of
## user-level perms (one key, one repo, no humans involved). Repos
## that genuinely need one would use a fine-grained PAT or a GitHub
## App installation instead. PATCH-settable; live-tested 2026-05.
readonly POLICY_MEMBERS='{
   default_repository_permission: "read",
   members_can_create_repositories: false,
   members_can_create_public_repositories: false,
   members_can_create_private_repositories: false,
   deploy_keys_enabled_for_repositories: false
}'
readonly POLICY_MEMBERS_ENDPOINT_ORG='/orgs/__ORG__'
readonly POLICY_MEMBERS_METHOD='PATCH'
readonly POLICY_MEMBERS_LABEL='members policy (default-perm=read, no member create, no deploy keys)'

## --- UI-only policies ---

## PATCH /orgs/{org} accepts no setter for two_factor_requirement_enabled
## (read-only on PATCH; verified against the OpenAPI spec). Per the
## Nov 2024 GA change, enabling does NOT remove non-compliant members -
## they keep membership but are blocked from org resources until they
## enroll.
readonly POLICY_2FA_UI_URL='https://github.com/organizations/__ORG__/settings/security'
readonly POLICY_2FA_LABEL='2FA enforcement must be set via UI'

## REST exposes GETs for pending requests and approved PATs but no
## stable setters.
readonly POLICY_PAT_UI_URL='https://github.com/organizations/__ORG__/settings/personal-access-tokens'
readonly POLICY_PAT_LABEL='PAT policy toggles must be set via UI'

readonly POLICY_APPS_UI_URL_INSTALLATIONS='https://github.com/organizations/__ORG__/settings/installations'
readonly POLICY_APPS_UI_URL_OAUTH='https://github.com/organizations/__ORG__/settings/oauth_application_policy'
readonly POLICY_APPS_LABEL='GitHub App / OAuth App policies must be set via UI'

## --- SOURCE vs MIRROR diffs ---
##
##                       SOURCE  MIRROR  why
##   has_wiki              off    off    we don't use GH wikis
##   has_issues             on    off    MIRROR: file upstream
##   has_projects           on    off    MIRROR: project board upstream
##   has_discussions        on    off    MIRROR: discussions upstream
##   allow_forking         n/a    n/a    handled at org level only;
##                                       per-repo PATCH triggers HTTP
##                                       422 ("organization does not
##                                       allow private repository
##                                       forking") and rolls back the
##                                       whole request. Org-level
##                                       'forking_allowed_for_private_repositories'
##                                       is the actual gate.
##   ruleset bypass    OrgAdmin  OrgAdmin   SOURCE: org-owner-only
##                                          escape hatch for
##                                          unsigned historical
##                                          pushes (rule on SOURCE
##                                          + bypass = strict for
##                                          non-admins).
##                                          MIRROR: hotfix without
##                                          dropping the ruleset.
##                                          See
##                                          POLICY_RULESET_BYPASS_*
##                                          below.

## Per-repo PATCH body. apply_repo_policy in dm-github-org-policy
## pivots on org_kind to choose between SOURCE and MIRROR.
##
## security_and_analysis is included so the single PATCH /repos also
## enables secret scanning + push protection on Free orgs (the paid
## org-level Code Security Configurations API rejects this body on
## Free, but the per-repo PATCH equivalent does not - empirically
## verified). Bundling avoids a second round-trip per repo.
readonly POLICY_REPO_SOURCE='{
   has_wiki: false,
   has_issues: true,
   security_and_analysis: {
      secret_scanning: { status: "enabled" },
      secret_scanning_push_protection: { status: "enabled" }
   }
}'
readonly POLICY_REPO_SOURCE_ENDPOINT='/repos/__OWNER__/__REPO__'
readonly POLICY_REPO_SOURCE_METHOD='PATCH'
readonly POLICY_REPO_SOURCE_LABEL='SOURCE: wiki=off, issues=on, secret-scan on'

readonly POLICY_REPO_MIRROR='{
   has_wiki: false,
   has_issues: false,
   has_projects: false,
   has_discussions: false,
   security_and_analysis: {
      secret_scanning: { status: "enabled" },
      secret_scanning_push_protection: { status: "enabled" }
   }
}'
readonly POLICY_REPO_MIRROR_ENDPOINT='/repos/__OWNER__/__REPO__'
readonly POLICY_REPO_MIRROR_METHOD='PATCH'
readonly POLICY_REPO_MIRROR_LABEL='MIRROR: wiki/issues/projects/discussions off, secret-scan on'

## SOURCE-only. MIRROR running these would duplicate every alert the
## canonical SOURCE repo already raises.
##
## Endpoint takes a body but accepts an empty object; we pass '{}' so
## the policy_apply_repo dispatcher (which always sends body via
## jq -n) works without a no-body special case.
readonly POLICY_REPO_DEPENDABOT_ALERTS='{}'
readonly POLICY_REPO_DEPENDABOT_ALERTS_ENDPOINT='/repos/__OWNER__/__REPO__/vulnerability-alerts'
readonly POLICY_REPO_DEPENDABOT_ALERTS_METHOD='PUT'
readonly POLICY_REPO_DEPENDABOT_ALERTS_LABEL='enable Dependabot alerts'

## SOURCE-only. Requires Dependabot alerts already enabled.
readonly POLICY_REPO_DEPENDABOT_FIXES='{}'
readonly POLICY_REPO_DEPENDABOT_FIXES_ENDPOINT='/repos/__OWNER__/__REPO__/automated-security-fixes'
readonly POLICY_REPO_DEPENDABOT_FIXES_METHOD='PUT'
readonly POLICY_REPO_DEPENDABOT_FIXES_LABEL='enable Dependabot security updates'

## Private Vulnerability Reporting (PVR) is NOT enabled anywhere.
## Canonical disclosure for Kicksecure / Whonix is the wiki linked
## from org-ai-assisted/.github/SECURITY.md. PVR would split the
## inbox between that wiki flow and a parallel GitHub-side flow. The
## DELETE side (POLICY_REPO_PVR_OFF below) is applied on both SOURCE
## and MIRROR every --apply; no PUT-style enable constants exist.

## --- Account-wide settings, applied by dm-github-personal-policy
## once per --apply (NOT per repo). Endpoints use the token-authed
## user implicitly; no __OWNER__ placeholder. Caller must guard
## "authed user == target_user" before calling.

## Token needs user:email scope; on classic PATs missing it the call
## returns HTTP 404 ("Not Found") - the documented "scope insufficient"
## failure mode.
readonly POLICY_USER_EMAIL_PRIVATE='{
   visibility: "private"
}'
readonly POLICY_USER_EMAIL_PRIVATE_ENDPOINT='/user/email/visibility'
readonly POLICY_USER_EMAIL_PRIVATE_METHOD='PATCH'
readonly POLICY_USER_EMAIL_PRIVATE_LABEL='hide primary email from public profile'

## --- MIRROR-only counterparts that actively turn the same three
## features OFF. dm-github-org-policy fans these out on kind=='mirror'
## so every --apply reconciles the state.
##
## G-035: see agents/github-policy-canonical-vs-mirror.md.

readonly POLICY_REPO_DEPENDABOT_ALERTS_OFF='{}'
readonly POLICY_REPO_DEPENDABOT_ALERTS_OFF_ENDPOINT='/repos/__OWNER__/__REPO__/vulnerability-alerts'
readonly POLICY_REPO_DEPENDABOT_ALERTS_OFF_METHOD='DELETE'
readonly POLICY_REPO_DEPENDABOT_ALERTS_OFF_LABEL='disable Dependabot alerts (mirror)'

readonly POLICY_REPO_DEPENDABOT_FIXES_OFF='{}'
readonly POLICY_REPO_DEPENDABOT_FIXES_OFF_ENDPOINT='/repos/__OWNER__/__REPO__/automated-security-fixes'
readonly POLICY_REPO_DEPENDABOT_FIXES_OFF_METHOD='DELETE'
readonly POLICY_REPO_DEPENDABOT_FIXES_OFF_LABEL='disable Dependabot security updates (mirror)'
readonly POLICY_REPO_DEPENDABOT_FIXES_OFF_EXTRA_OK_STATUS='422'

## Applied on BOTH SOURCE and MIRROR. See the PVR rationale block above.
readonly POLICY_REPO_PVR_OFF='{}'
readonly POLICY_REPO_PVR_OFF_ENDPOINT='/repos/__OWNER__/__REPO__/private-vulnerability-reporting'
readonly POLICY_REPO_PVR_OFF_METHOD='DELETE'
readonly POLICY_REPO_PVR_OFF_LABEL='disable private vulnerability reporting'

## Branch + tag ruleset bypass actor list.
##
##   SOURCE = [OrganizationAdmin]
##     Org owners can override. Originally '[]' for "canonical
##     history is sacred", but with required_signatures on SOURCE
##     and a large amount of unsigned historical commits, every
##     maintainer push got blocked. The bypass lets the org owner
##     keep pushing unsigned commits while still blocking the same
##     for non-admin contributors. The MIRROR shape is reused
##     verbatim; same recovery-from-stuck-state benefit applies.
##   MIRROR = [OrganizationAdmin]
##     Org owners can override. If a re-fork or hotfix needs a
##     force-push or unsigned commit, do it without first dropping
##     the ruleset.
##   PERSON = computed at --apply time, not a static constant.
##     dm-github-personal-policy resolves target_user -> user_id via
##     GET /users/{login} and builds
##     [{actor_id: <id>, actor_type: "User", bypass_mode: "always"}].
##     Static org-level OrganizationAdmin does not apply to
##     user-owned repos (no org); the User actor_type is the
##     repo-owner-bypass equivalent. The bypass lets the maintainer
##     push unsigned commits to their own mirror repos without
##     dropping required_signatures for any other collaborator with
##     write access. bypass_mode: "always" exempts the actor from
##     EVERY rule (deletion + non_fast_forward + required_signatures);
##     that broader scope is acceptable on PERSON because the
##     maintainer is the repo owner and could perform deletion /
##     force-push via the GitHub UI regardless.
##   BOT = '[]'. The BOT rules (deletion + non_fast_forward, no
##     required_signatures) do not block legitimate bot pushes;
##     granting the bot account a User bypass would let it
##     force-push and delete branches on its own repos, defeating
##     the rules' purpose. dm-github-personal-policy hardcodes '[]'
##     when target_kind=bot and skips the user_id lookup entirely.
##
## Plan-independent: works at org level (PAID PLAN ONLY, Team+) and
## repo level (Free for public repos). On Free the org-level upserts
## in dm-github-org-policy are commented out; the per-repo loop still
## consumes these.
readonly POLICY_RULESET_BYPASS_SOURCE='[{"actor_id":1,"actor_type":"OrganizationAdmin","bypass_mode":"always"}]'
readonly POLICY_RULESET_BYPASS_MIRROR='[{"actor_id":1,"actor_type":"OrganizationAdmin","bypass_mode":"always"}]'

## Branch + tag ruleset body shapes. POSTed to
## /orgs/{org}/rulesets or /repos/{owner}/{repo}/rulesets via the
## policy_*_ruleset_body factories in github-policy-lib.bsh; the
## factories inject $name, $bypass, and $rules.
##
## Two base rules everywhere:
##   - deletion           block deletion of the protected ref
##   - non_fast_forward   block force-push (rewriting history)
##
## A third optional rule pivots on role:
##   - required_signatures   require GPG-signed commits / tags
##
## POLICY_RULESET_RULES_<ROLE> below selects the rules array per
## role. SOURCE and PERSON include required_signatures; MIRROR and
## BOT omit it. Rationale:
##   - MIRROR (org-ai-assisted) and BOT (assisted-by-ai and peers)
##     push commits via AI-assisted automation that does not own a
##     GPG key matching the bot's GitHub-verified identity, so
##     required_signatures on those roles blocks every legitimate
##     bot push.
##   - SOURCE (Kicksecure / Whonix) and PERSON (adrelanos personal
##     mirror) keep the rule. SOURCE pairs it with an
##     OrganizationAdmin bypass; PERSON pairs it with a User-actor
##     bypass scoped to target_user's GitHub user_id (resolved at
##     --apply time, see dm-github-personal-policy). Either bypass
##     lets the maintainer push unsigned commits (whether
##     backfilling unsigned history or going forward without a
##     verifiable GPG key) without dropping the rule for non-admin
##     collaborators with write access.
##
## Operator note on required_signatures + GitHub's UI merge buttons
## (applies to SOURCE / PERSON):
##   "Merge pull request" and "Squash and merge" re-sign the produced
##     commit with GitHub's web-flow GPG key, so they keep working on
##     PRs with unsigned contributor commits.
##   "Rebase and merge" does NOT re-sign rebased commits and will
##     fail against this ruleset when the PR carries unsigned commits.
##     Use Merge or Squash for fork PRs.
##
## Tag ruleset matters specifically: without it, anyone with push
## access could move a release tag like v1.0 to a different commit,
## silently changing what 'v1.0' refers to.
##
## Branch ruleset deliberately does NOT include:
##   - pull_request          our maintainers push directly to master
##   - required_status_checks   per-repo CI coupling - belongs in
##                              repo-level config
##   - required_linear_history  breaks some merge workflows

## Role-keyed rules arrays. Resolved by the apply scripts via
## POLICY_RULESET_RULES_${kind^^} (same dynamic-variable-name pattern
## as POLICY_RULESET_BYPASS_${kind^^}). Unlike the body templates
## above, these are strict JSON (quoted keys) because they flow into
## jq via --argjson, which requires JSON input - not the relaxed jq
## syntax the body templates use.
readonly POLICY_RULESET_RULES_SOURCE='[
   {"type": "deletion"},
   {"type": "non_fast_forward"},
   {"type": "required_signatures"}
]'
readonly POLICY_RULESET_RULES_MIRROR='[
   {"type": "deletion"},
   {"type": "non_fast_forward"}
]'
readonly POLICY_RULESET_RULES_PERSON='[
   {"type": "deletion"},
   {"type": "non_fast_forward"},
   {"type": "required_signatures"}
]'
readonly POLICY_RULESET_RULES_BOT='[
   {"type": "deletion"},
   {"type": "non_fast_forward"}
]'

## PAID PLAN ONLY: org-scoped ruleset (uses ~ALL repository_name
## condition). Sent to /orgs/{org}/rulesets, Team+ only.
# readonly POLICY_BRANCH_RULESET_BODY_ORG='{
#    name: $name,
#    target: "branch",
#    enforcement: "active",
#    bypass_actors: $bypass,
#    conditions: {
#       ref_name: { include: ["~DEFAULT_BRANCH"], exclude: [] },
#       repository_name: { include: ["~ALL"], exclude: [] }
#    },
#    rules: $rules
# }'

readonly POLICY_BRANCH_RULESET_BODY_REPO='{
   name: $name,
   target: "branch",
   enforcement: "active",
   bypass_actors: $bypass,
   conditions: {
      ref_name: { include: ["~DEFAULT_BRANCH"], exclude: [] }
   },
   rules: $rules
}'

## PAID PLAN ONLY: see POLICY_BRANCH_RULESET_BODY_ORG note above.
# readonly POLICY_TAG_RULESET_BODY_ORG='{
#    name: $name,
#    target: "tag",
#    enforcement: "active",
#    bypass_actors: $bypass,
#    conditions: {
#       ref_name: { include: ["~ALL"], exclude: [] },
#       repository_name: { include: ["~ALL"], exclude: [] }
#    },
#    rules: $rules
# }'

readonly POLICY_TAG_RULESET_BODY_REPO='{
   name: $name,
   target: "tag",
   enforcement: "active",
   bypass_actors: $bypass,
   conditions: {
      ref_name: { include: ["~ALL"], exclude: [] }
   },
   rules: $rules
}'

## ============================================================================
## Personal-policy bodies - used by dm-github-personal-policy
## ============================================================================

## Disable Actions entirely on the repo. Personal account is a
## backup/mirror; CI runs upstream. Cuts against malicious-fork-PR
## or accidentally-triggered runs and saves the operator's free CI
## minutes.
readonly POLICY_ACTIONS_DISABLED='{
   enabled: false
}'
readonly POLICY_ACTIONS_DISABLED_ENDPOINT_REPO='/repos/__OWNER__/__REPO__/actions/permissions'
readonly POLICY_ACTIONS_DISABLED_METHOD='PUT'
readonly POLICY_ACTIONS_DISABLED_LABEL='actions enabled=false (CI runs disabled)'

## --- PERSON vs BOT diffs ---
##
##                       PERSON  BOT  why
##   has_wiki              off   off   we don't use GH wikis
##   has_issues            off   off   file upstream
##   has_projects          off   off   project boards belong upstream
##   has_discussions       off   off   discussions belong upstream
##   allow_forking          on    on   GitHub forbids tightening on
##                                     user-owned repos (HTTP 422);
##                                     org-level MIRROR policy enforces
##                                     it where it can stick
##   secret_scanning        on    on
##   secret_scanning_push_protection  on    on
##
## PERSON and BOT bodies are currently identical. Split kept for
## future divergence (and so the user_kind dispatcher in
## dm-github-personal-policy stays uniform with the org-side
## SOURCE/MIRROR pattern).

readonly POLICY_PERSONAL_REPO_PERSON='{
   has_wiki: false,
   has_issues: false,
   has_projects: false,
   has_discussions: false,
   security_and_analysis: {
      secret_scanning: { status: "enabled" },
      secret_scanning_push_protection: { status: "enabled" }
   }
}'
readonly POLICY_PERSONAL_REPO_PERSON_ENDPOINT_REPO='/repos/__OWNER__/__REPO__'
readonly POLICY_PERSONAL_REPO_PERSON_METHOD='PATCH'
readonly POLICY_PERSONAL_REPO_PERSON_LABEL='PERSON: settings (wiki/issues/projects/discussions off, secret-scan on)'

## allow_forking NOT in this body: GitHub rejects the entire PATCH
## with HTTP 422 ("Allow forks can only be changed on org-owned
## repositories") whenever it appears on a user-owned repo, even when
## the value is already what the request asks for. Forks-of-forks
## risk on user-owned mirrors is mitigated upstream by the org-level
## MIRROR policy.
readonly POLICY_PERSONAL_REPO_BOT='{
   has_wiki: false,
   has_issues: false,
   has_projects: false,
   has_discussions: false,
   security_and_analysis: {
      secret_scanning: { status: "enabled" },
      secret_scanning_push_protection: { status: "enabled" }
   }
}'
readonly POLICY_PERSONAL_REPO_BOT_ENDPOINT_REPO='/repos/__OWNER__/__REPO__'
readonly POLICY_PERSONAL_REPO_BOT_METHOD='PATCH'
readonly POLICY_PERSONAL_REPO_BOT_LABEL='BOT: settings (wiki/issues/projects/discussions off, secret-scan on)'

## DELETE is idempotent in spirit: 204 if a site existed, 404 if no
## site. policy_api_call's extra-ok-status machinery treats 404 as
## success. Not a permanent gate - any future Pages workflow run will
## recreate the site.
readonly POLICY_PAGES_DELETE_ENDPOINT_REPO='/repos/__OWNER__/__REPO__/pages'
readonly POLICY_PAGES_DELETE_METHOD='DELETE'
readonly POLICY_PAGES_DELETE_EXTRA_OK_STATUS='404'
readonly POLICY_PAGES_DELETE_LABEL='delete Pages site (if any)'

## ============================================================================
## Metadata-sync bodies - used by dm-github-org-metadata-sync
## ============================================================================
##
## Bodies are jq templates with caller-supplied --arg / --argjson:
##   POLICY_REPO_METADATA needs --arg desc --arg home
##   POLICY_REPO_TOPICS   needs --argjson topics '<JSON array>'
##
## Built and PATCH/PUTed via policy_api_call directly (not
## policy_apply_repo) because the dispatcher does not forward
## per-repo --arg values.
##
## Empty $desc / $home pass through as "" and GitHub clears the
## stored field; per repo-metadata.bsh docstring this is the
## intended "unset" form.
readonly POLICY_REPO_METADATA='{
   description: $desc,
   homepage: $home
}'
readonly POLICY_REPO_METADATA_ENDPOINT_REPO='/repos/__OWNER__/__REPO__'
readonly POLICY_REPO_METADATA_METHOD='PATCH'
readonly POLICY_REPO_METADATA_LABEL='description + homepage'

readonly POLICY_REPO_TOPICS='{
   names: $topics
}'
readonly POLICY_REPO_TOPICS_ENDPOINT_REPO='/repos/__OWNER__/__REPO__/topics'
readonly POLICY_REPO_TOPICS_METHOD='PUT'
readonly POLICY_REPO_TOPICS_LABEL='topics'

## ============================================================================
## SOURCE-side UI-only operator flips
## ============================================================================
##
## Dependabot + Code-scanning settings whose desired state is known
## but has no documented REST setter as of 2026-05. The scripts emit
## a `skip: ... see <URL>` line on every --apply so the operator can
## flip each manually. All apply to SOURCE only - Dependabot is off
## entirely on MIRROR / PERSON / BOT, so the org-level grouping and
## per-repo auto-triage / delegated-dismissal toggles are moot
## there. Code-scanning query-suite recommendation is harmless on
## MIRROR but never matters there because MIRROR repos use the
## reusable `codeql.yml` workflow (advanced setup), which carries
## its own `queries:` value and ignores the org default-setup
## recommendation.
##
## See agents/github-policy-canonical-vs-mirror.md "SOURCE-side
## UI-only operator flips" for the per-setting rationale.

readonly POLICY_GROUPED_SECURITY_UPDATES_LABEL='Dependabot grouped security updates: enable in UI (one PR per ecosystem instead of one per dep)'
readonly POLICY_GROUPED_SECURITY_UPDATES_UI_URL='https://github.com/organizations/__ORG__/settings/security_analysis'

readonly POLICY_CODE_SCANNING_QUERY_SUITE_LABEL='Code scanning: recommend security-extended query suite (default-setup repos only)'
readonly POLICY_CODE_SCANNING_QUERY_SUITE_UI_URL='https://github.com/organizations/__ORG__/settings/security_analysis'

readonly POLICY_AUTO_TRIAGE_DISMISS_DEV_LABEL='Auto-triage rule "Dismiss low-impact dev-scoped" must be OFF (security project: see every alert)'
readonly POLICY_AUTO_TRIAGE_DISMISS_DEV_UI_URL_REPO='https://github.com/__OWNER__/__REPO__/settings/dependabot_rules'

readonly POLICY_AUTO_TRIAGE_DISMISS_MALWARE_LABEL='Auto-triage rule "Dismiss package malware alerts" must be OFF (never auto-dismiss malware)'
readonly POLICY_AUTO_TRIAGE_DISMISS_MALWARE_UI_URL_REPO='https://github.com/__OWNER__/__REPO__/settings/dependabot_rules'

readonly POLICY_DELEGATED_DISMISSAL_LABEL='Prevent direct Dependabot alert dismissals (delegated dismissal): enable in UI'
readonly POLICY_DELEGATED_DISMISSAL_UI_URL_REPO='https://github.com/__OWNER__/__REPO__/settings/security_analysis'
