# =============================================================================
# config/publish.yaml
# -----------------------------------------------------------------------------
# Rules for the publish pipeline (scripts/publish*.py) that exports vetted vault
# notes from 90_Public/** into a website content directory.
#
# CORE GATE (ALL must be true for a file to be eligible to publish):
#   1. frontmatter.visibility is in require_visibility  (public-ready | published)
#   2. frontmatter.publish is true
#   3. the sanitize pass passes (no denylisted bot names / secrets leak)
#   AND
#   4. require_human_approval is honored -- a human explicitly approves the run.
#
# GLOBAL POLICY (mirrors CLAUDE.md / README / HERMES):
#   default private; NEVER auto-publish. private -> public requires explicit
#   human approval. This file cannot, by itself, push anything live; it only
#   describes the gate the tooling enforces.
#
# YAML SUBSET: 2-space indent, block maps/seqs, plain/quoted scalars, empty
# collections only as inline [] / {}. No anchors/flow maps/folding.
# =============================================================================

version: 1
updated: 2026-06-23

# --- Eligibility gate --------------------------------------------------------
# Visibility values that are allowed to leave the vault. Anything else is
# private/internal/candidate/public-draft and is NEVER exported.
require_visibility:
  - public-ready
  - published
require_publish_flag: true     # frontmatter `publish:` must be literally true
require_sanitize_pass: true    # sanitize step (below) must report clean

# --- What gets considered ----------------------------------------------------
# Only files under 90_Public are ever candidates. Drafts elsewhere are ignored
# regardless of their frontmatter.
source_globs:
  - '90_Public/**/*.md'
# Globs explicitly excluded even within 90_Public (work-in-progress areas).
exclude_globs:
  - '90_Public/exports/**'      # exports are OUTPUT, never re-published as source
  - '90_Public/**/_drafts/**'

# --- Destinations ------------------------------------------------------------
# website_content_dir is a placeholder the operator MUST set to the real site
# repo content folder before any publish run. export_dir is the in-vault staging
# copy of what was published (audit trail of rendered output).
website_content_dir: '<set me>'          # e.g. ../karnellabs-site/content/notes
export_dir: 90_Public/exports

# --- Frontmatter transform ---------------------------------------------------
# Maps internal vault frontmatter keys -> website frontmatter keys on export.
# Keys absent from a note are simply skipped. Internal-only keys NOT listed here
# (visibility, publish, sources, status, etc.) are STRIPPED from public output.
frontmatter_transform:
  title: title
  tags: tags
  created: date
  updated: lastmod
  type: kind
  summary: description
# Keys to forcibly drop even if present (defense-in-depth against leaks).
frontmatter_strip:
  - visibility
  - publish
  - sources
  - status

# --- Sanitize pass -----------------------------------------------------------
# Runs before export. Fails the file (and, per fail_run_on_violation, the run)
# if any denylisted internal bot/automation name appears in body or frontmatter.
# The authoritative denylist lives in skill-policy.yaml so sanitize and policy
# agree; this references it rather than duplicating.
sanitize:
  bot_name_denylist_source: config/skill-policy.yaml   # -> bot_name_denylist
  # also_deny entries go through the bot-name word-boundary matcher, so they
  # suit literal tokens (env-var names) only. Filesystem paths are handled by
  # the dedicated `windows_path` detector in scripts/sanitize_public.py, not
  # this list.
  also_deny:
    - 'KARNEL_DISCORD_WEBHOOK'    # never let an env-var name leak into public
  fail_run_on_violation: true     # one tainted file aborts the whole publish run
  redact_placeholder: '[redacted]'

# --- Git behavior ------------------------------------------------------------
# How published output reaches the website repo. 'pr' is the safe default: open
# a pull request for human merge. 'commit' commits directly; 'none' writes files
# only (no VCS action). Scripts here NEVER push without this being set + approved.
git_mode: pr                      # ENUM: pr | commit | none
git_branch_prefix: 'publish/'     # used when git_mode == pr
git_commit_subject: 'content: publish vault notes ({date})'

# --- Human approval (non-negotiable) -----------------------------------------
# Even with every gate green, the publish run must be explicitly approved by a
# human. Tooling must refuse to proceed in unattended mode. This enforces the
# private -> public approval rule from the global policy.
require_human_approval: true
approval_note: 'private -> public is a human decision. No unattended publish, ever.'
