# SDLC Workflow Module Configuration
# Hand-authored custom BMAD module (format mirrors _bmad/<module>/config.yaml, v6.8.0).
# Source of truth lives in {project-root}/skills/ and survives BMAD updates.

module_name: sdlc
module_title: "SDLC Workflow"
methodology: gated-team-multirepo-sdlc

# Where epic "thinking" artifacts and per-epic state live (the product repo).
product_root: "{project-root}"
epics_folder: "{project-root}/epics"

# Project discovery (yad-discovery) — the OPTIONAL front-zero, run once per project (greenfield AND
# brownfield). It is modelled as the reserved "epic zero" `EP-discovery` so the entire review gate +
# PR/MR bridge + CI sync + `yad next` operate on it unchanged. A 2-step author→review chain whose
# review binds to the whole artifact set; on approval it terminates at `discovery-done` (no build half).
# Output: a phased roadmap (incl. MVP) that each feature epic reads (yad-epic Step 2c) — reference-only,
# never auto-seeds epics.
discovery:
  epic_id: "EP-discovery"                              # reserved id; yad-epic/yad-analysis never pick it
  location: "{project-root}/epics/EP-discovery/"       # discovery artifacts + ledger live here
  optional: true                                       # skip-able; a team that knows what to build starts at yad-epic
  modes: [greenfield, brownfield]                       # current-state is code-aware in brownfield; competitor study in both
  artifacts: [market-research.md, competitor-analysis.md, current-state.md, feasibility.md, requirements.md, roadmap.md]

# Core configuration values (inherited convention from _bmad/config.toml).
project_name: yadflow
communication_language: English
document_output_language: English
output_folder: "{project-root}/_bmad-output"

# Default dials applied to every new step (build plan §2).
defaults:
  assistance: review          # none | review | heavy
  automation: human_approve   # human_approve | machine_advance
  # Front steps (discovery [optional front-zero], analysis [optional], epic, architecture,
  # ui-design [optional — skippable N/A for UI-less epics via `yad skip <epic> ui-design --reason "<why>"`],
  # stories, test-cases) are locked to human_approve and may NOT be set to machine_advance in this
  # version (build plan §1, §8.7).
  front_steps_locked: true
  # Each front authoring step opens its own branch at the start of the step (the <step> is the step id:
  # discovery | analysis | epic | architecture | ui-design | stories | test-cases). Git/greenfield-safe; distinct
  # from the bridge's review branch (hub.artifact_branch). See yad-epic/references/state-schema.md.
  front_authoring_branch: "<step>/EP-<slug>"

# Team review gate defaults (build plan §3 piece 2, §4).
review_gate:
  default_reviewers: 1          # non-owner reviewers required (in addition to 1 owner) => owner + 1 reviewer
  # Solo mode (a lone developer): a relaxed, opt-in safety guarantee, recorded per-project in
  # .sdlc/hub.json (`solo: true`). On GitHub you CANNOT approve your own PR, so requiring an approval
  # would deadlock a solo user. Solo waives the APPROVAL requirement only — the review PR/MR and its
  # MERGE stay (CI still runs on the PR, and the merge is what advances the step). Net: the gate passes
  # on `merged + all comment threads resolved`, no approval needed. NOT a default; team gates are
  # unchanged. (Branch protection must not "require approvals", or the solo dev's own merge is blocked —
  # `yad doctor` warns when it does.)
  solo: false
  escalate_when: [contract, auth, payments]   # escalate to domain owners
  # PR-driven automation (the `yad gate` CLI). With a hub platform, the review rides the per-step
  # PR/MR: `yad gate sync` maps platform reviews/threads into the file ledger and the step
  # AUTO-ADVANCES on merge, once (a) the reviewer rule is met, (b) every comment thread is resolved,
  # and (c) the review PR/MR is merged. The merge is the human approval act, so front steps still never
  # machine_advance. The file ledger stays the source of truth; no platform / no gh|glab => file-only.
  advance_on: merge             # merge of the approved, fully-resolved review PR advances the step
  revoke_on: artifact-change    # re-hash the artifact (contract surface for architecture); a changed
                                # hash drops the bound approvals so reviewers re-approve. NOT per-commit.
  block_on_unresolved_comments: true   # any unresolved thread / CHANGES_REQUESTED holds it in_review
  # Review Companion engagement (yad-review-companion). Each approval records engagement: verified|none
  # — verified when reviewed through the companion (trailer/cards/chat), none for a bare click. SOFT by
  # default: both count, a bare approve still passes but draws a friendly public @-mention nudge so
  # review quality is VISIBLE without blocking anyone ("visible, not impossible"). Flip to true to make
  # only verified approvals count (the signal is gameable by design — it raises the cost of a rubber-
  # stamp, it does not prove a human read the artifact). Persisted per-project in hub.json as
  # `review.requireEngagement`; companion comments carry `<!-- yad:noblock -->` so they never block.
  require_engagement: false
  # Pair Review (yad-pair-review) — the guided, two-way, TEACHING walkthrough; the AI-driven 5th
  # companion face. The AI walks the human through the change one risk-ordered stop at a time, asks
  # Socratic questions, and both sign off when satisfied; the session doubles as a learning session and
  # records the engineer's review-skill growth in the local-only yad-learn ledger. SOFT and additive —
  # it NEVER blocks a merge or gate (no strict switch); it rides the same `engagement: verified` signal
  # as the companion and surfaces genuine concerns as normal blocking comments. `mode: optional` is the
  # default posture (offered, never required). The quiz/comprehension signal reuses learning.capabilities.
  pair_review:
    enabled: true
    mode: optional              # optional | encouraged — never `required` (it can never gate)
    never_blocks: true          # invariant: a pair session is advisory; the gate predicate is untouched
    rubric: review-rubric.md    # the transferable review method (skills/yad-pair-review/references/)

# Build half (Phase 3). Code repos are SEPARATE git repos (one .git each), not subfolders
# of the product repo — faithful to "per-repo specs in each code repo, contract singular in the
# product repo" (phase-3-build-plan.md, Cross-cutting). Documentation-as-config for yad-spec (Step A).
build:
  code_repos_root: "{project-root}/demo-repos"   # throwaway demo code repos for this build half
  feature_id: story_id          # specs/<story-id>/ — pinned to the permanent story ID, not Spec Kit's auto-slug
  spec_layout: speckit          # follow Spec Kit's native spec/plan/tasks layout
  speckit_ceremony: [specify, clarify, plan, analyze, checklist, tasks]   # heavy run, once per story per repo
  # Step B (yad-implement) — the light per-task loop. One atomic task = one branch = one PR/MR.
  branch_convention: "feat/<story-id>-<task-id>-<short-slug>"   # e.g. feat/EP-istifta-inquiries-S01-T01-create-inquiry
  commit_task_trailer: "Task: <story-id>-<task-id>"             # final commit trailer; anchors the spec-link check (Step C)
  contract_change_trailer: "Contract-Change: yes"              # ONLY when the locked contract surface is touched (routes back to architecture gate)
  # Commit subject + PR/MR title style (Conventional Commits — see CONTRIBUTING.md). PRs are squash-merged,
  # so the title becomes the subject; both follow one rule. Type lowercase; description lowercase + imperative
  # + no trailing period; proper nouns/acronyms keep their case.
  commit_subject_style: "<type>: <lowercase imperative description, no trailing period>"   # types: feat|fix|docs|refactor|test|perf|build|ci|chore|revert
  pr_title_style: same_as_commit_subject                       # one atomic task = one branch = one PR/MR; title defaults to the task's commit subject
  # Commit ownership + per-commit AI co-author (yad-implement installs the .gitmessage template).
  # The human git author OWNS the commit; the assisting AI is recorded per-commit as a Co-Authored-By
  # trailer, chosen by the owner from `ai_coauthor.allowed`. The AI is never the author. `none` is an
  # explicit human-only choice. Trailer order is Task: -> Contract-Change: (if any) -> Co-Authored-By:.
  commit_owner: git_author
  ai_coauthor:
    trailer: "Co-Authored-By"
    required: false                  # the trailer is optional; owners pick `none` for human-only commits
    allowed:
      - { id: claude,     name: "Claude",             email: "noreply@anthropic.com" }
      - { id: copilot,    name: "GitHub Copilot",     email: "copilot@users.noreply.github.com" }
      - { id: cursor,     name: "Cursor",             email: "noreply@cursor.com" }
      - { id: coderabbit, name: "CodeRabbit",         email: "noreply@coderabbit.ai" }
      - { id: none,       name: "(no AI assistance)", email: "" }
  # Step C (yad-checks) — the CI gates that must pass before merge. CI-agnostic bash in checks/.
  # Phase 6 adds three thread-aware gates: lineage-check (every change links a real threaded epic),
  # epic-open (a SEALED epic — all stories shipped — refuses new behaviour, forcing a change-epic so the
  # front artifacts can never go stale), and reconcile-debt (a hotfix's ship-first debt blocks the next
  # change on its thread until paid). See the `change:` block below.
  gates: [spec-link, contract-check, build-test-lint, lineage-check, epic-open, reconcile-debt]
  # Pattern gates — commit subject + PR/MR title + PR/MR template-usage. Profile-aware (code|hub): code
  # repos validate the Conventional-Commits / task-PR conventions; the product hub validates its
  # artifact-review conventions (review/EP-<slug>/<artifact>, the hub PR template). verified-commits is
  # the separate security gate (signature + roster-known author). All blocking in CI; both repos + hub.
  pattern_gates: [commit-message, pr-title, pr-template]
  ci_platforms: [github, gitlab]   # both wired from skills/yad-checks/templates/; GitHub is this product repo's platform
  contract_surface_glob: "specs/*/contracts/**"   # the repo's quoted contract slice; a diff here needs Contract-Change + a re-locked contract
  # Step D (yad-pr-template) — platform-matched PR/MR template + risk routing.
  pr_templates:
    github: ".github/pull_request_template.md"
    gitlab: ".gitlab/merge_request_templates/Default.md"
    hub:                                # front-half artifact-review PR/MR on the product hub (yad-pr-template repo:hub)
      github: ".github/pull_request_template.md"
      gitlab: ".gitlab/merge_request_templates/Default.md"
  risk_levels: [low, medium, high]   # high (or a contract/auth/payments surface) routes to domain owners (yad-review-gate escalation)
  # Step E (yad-engineer-review) — AI review (advisory) + engineer review (the human gate) + merge.
  ai_review: coderabbit            # advisory first pass; never the authority (.coderabbit.yaml)
  # Append-only ship ledger (back-half analogue of approvals.json), stored shard-then-fold: writers add one
  # shard per ship under build_log_dir; `yad tidy up` folds a SHIPPED story's shards into build_log.
  # READERS MUST UNION the two (dedupe by (story, task, repo); a shard wins) — build_log alone omits every
  # unfolded ship, e.g. a `yad checkpoint --retro-ship` backfill or any ship on an `in-build` story.
  build_log: "epics/EP-<slug>/.sdlc/build-log.json"   # folded ships
  build_log_dir: "epics/EP-<slug>/.sdlc/build-log/"   # one <story>-<task>-<repo>.json shard per ship
  story_build_states: [in-build, shipped]   # in-build = some tasks shipped; shipped = all tasks in tasks.md shipped
  # Backfill (yad-backfill) — specs for already-built features in an existing repo.
  backfill:
    tool: repomix                  # the one true CLI subprocess: `npx repomix@latest` (Phase 0 / RESEARCH-NOTES §3)
    pack_flags: "--compress --include <globs> --include-logs --style markdown"   # one feature at a time; Secretlint by default
    spec_location: "specs/backfill/<feature>/spec.md"   # draft (verified: false) until a human approves
    gate_scope: touched-features   # block a change only until the features it touches have approved specs
    promote: yad-stub   # `yad-backfill promote EP-<slug>` flips a brownfield stub epic (change.stub) to a real, verified feature epic once its spec is approved

# Code context (yad-connect-repos) — the front/"brain" phases are made code-aware. Code repos are
# connected to the product hub once (or any time a new repo is added), and an AI-readable picture of
# each is cached so epic/architecture/ui/stories consider what already exists in the code. The product
# repo is the FRONT-PHASE TOOLCHAIN HUB: repomix (and Impeccable) are installed/run here and target the
# connected code repos by path — code repos need no install for this. (The build-half CI gates are the
# exception: they live INSIDE each code repo and run in that repo's CI — see build.gates above.)
code_context:
  registry: "{project-root}/.sdlc/repos.json"        # project-wide repo registry (NOT per-epic)
  cache_dir: "{project-root}/.sdlc/code-context"      # per-repo pack.md + code-map.md live here
  tools: [repomix, impeccable]                        # front-phase toolchain, installed in the product hub
  pack_flags: "--compress --include-logs --style markdown"   # reuse backfill flags; Secretlint by default
  staleness: head-sha                                 # stale when a repo's HEAD != registry syncedHead
  refresh: human                                      # a stale repo is a HUMAN decision: front-phase
                                                      # skills FLAG it and stop (pointing at
                                                      # `yad repo refresh <repo>`) — they never silently
                                                      # re-pack. `yad repo list` shows fresh/stale;
                                                      # `yad repo refresh` and `yad check --fix` re-pack.
  # `yad repo sync [<repo>]` (yad-sync-repos) switches every connected repo to its default_branch and
  # fast-forwards it from origin before work starts — working-tree only (never writes the registry);
  # a dirty repo is skipped, a diverged branch is left for manual resolution (fast-forward only).
  load_in_front_phases: true                          # epic, architecture, ui-design, stories read the maps
  # Auth: `yad-connect-repos` clones/fetches as the LOCAL user (SSH key or git credential helper),
  # works for both github and gitlab (and self-hosted), and stores NO tokens in the registry.
  platforms: [github, gitlab]

# Design tool (yad-connect-design) — the UI design step (yad-ui) materializes the actual feature design
# (mobile screens / web pages) inside a connected design tool, alongside ui-design.md + DESIGN.md. The
# tool is reached through its MCP (a harness MCP server, like Impeccable's slash-commands — NOT a CLI),
# detected per provider and DEGRADING to markdown-only when absent. Figma-first but PLUGGABLE: a
# design-tool adapter, like the github/gitlab platform adapter. Connection is one-per-project, recorded
# at setup; the per-epic screen->frame map (design-links.json) is written by yad-ui per epic.
design:
  registry: "{project-root}/.sdlc/design.json"       # project-wide design connection (NOT per-epic)
  tools: [figma, pencil]                              # supported adapters; an unknown tool falls back to `primary`
  primary: figma                                      # the default/named provider
  degrade: markdown-only                              # no tool / no MCP => yad-ui authors ui-design.md + DESIGN.md only
  links: "{project-root}/epics/EP-<slug>/.sdlc/design-links.json"   # per-epic screen->frame map (yad-ui)
  # Auth: `yad-connect-design` connects through the LOCAL user's own authenticated MCP session and stores
  # NO tokens in the registry (project_url/files are plain references, never credentials).
  auth: user

# Testing tool (yad-connect-testing) — the test-cases step (yad-test-cases) materializes the actual
# automation tests inside a connected testing tool, alongside test-cases.md. The tool is reached through
# its MCP (a harness MCP server, like the design tool — NOT a CLI), detected per provider and DEGRADING
# to artifacts-only when absent. Playwright-first but PLUGGABLE: a testing-tool adapter, like the
# design-tool adapter. Connection is one-per-project, recorded at setup; the per-epic case->test map
# (test-links.json) is written by yad-test-cases per epic.
testing:
  registry: "{project-root}/.sdlc/testing.json"      # project-wide testing connection (NOT per-epic)
  tools: [playwright, cypress, pytest, maestro]       # supported adapters; an unknown tool falls back to `primary`
  primary: playwright                                 # the default/named provider
  degrade: artifacts-only                             # no tool / no MCP => yad-test-cases authors test-cases.md only
  links: "{project-root}/epics/EP-<slug>/.sdlc/test-links.json"   # per-epic case->test map (yad-test-cases)
  # Auth: `yad-connect-testing` connects through the LOCAL user's own authenticated MCP session and stores
  # NO tokens in the registry (project_url/suites are plain references, never credentials).
  auth: user

# Learning tool (yad-connect-learning) — the cross-cutting LEARNING LAYER. At any SDLC stage a team member
# can invoke `yad-learn` to be tutored on a concept IN THE CONTEXT of what is being built, and the request
# is recorded in a personal, LOCAL-ONLY skills log (gitignored, never committed/pushed; surfaced read-only
# by yad-status for the local learner). The tool is
# DeepTutor (https://github.com/HKUDS/DeepTutor) — an Apache-2.0 Python CLI, reached as a SUBPROCESS like
# Repomix's `npx` (it has NO MCP), NOT the MCP shape of the design/testing tools. DeepTutor-first but
# PLUGGABLE: a learning-tool adapter. Connection is one-per-project, recorded at setup; the per-epic,
# per-member learning ledger (learning-records.json) + rendered tutorials are written by yad-learn.
learning:
  registry: "{project-root}/.sdlc/learning.json"     # project-wide learning connection (NOT per-epic) — the ONLY committed learning file (no secrets/personal data)
  tools: [deeptutor]                                  # supported adapters; an unknown tool falls back to `primary`
  primary: deeptutor                                  # the default/named provider
  degrade: harness-native                             # no deeptutor CLI => yad-learn tutors via the harness model
                                                      # reading the project artifacts directly (always works, never blocks)
  records: "{project-root}/epics/EP-<slug>/.sdlc/learning-records.json"   # per-epic learning ledger (yad-learn) — LOCAL-ONLY, gitignored, never committed/pushed
  artifacts: "{project-root}/epics/EP-<slug>/learning/"   # rendered tutorial markdown (yad-learn) — LOCAL-ONLY, gitignored, never committed/pushed
  capabilities: { explain: chat, deep: deep_research, quiz: deep_question }   # yad-learn mode -> deeptutor capability
  # Auth: `yad-connect-learning` uses the LOCAL user's own deeptutor CLI config/session and stores NO
  # tokens in the registry (kb name + sources are plain references, never credentials). Opt-in, never a gate.
  auth: user

# Interactive documentation (yad-connect-docs / yad-docs / yad-docs-overview / yad-docs-sync) — the
# generated React+Vite+Tailwind doc SITES. yad-docs builds a per-epic site (epics/EP-<slug>/docs-site/)
# from the authored artifacts, themed by the connected design system; yad-docs-overview builds the
# project SDLC-overview site (docs/sdlc-site/) from the pipeline definition, superseding docs/index.html.
# The deploy TARGET is a Pages host, auto-detected from hub.platform (github->github-pages,
# gitlab->gitlab-pages, null->build-only). The yad CLI does the npm build + Pages wiring + staleness;
# the content generation is the AI step. Docs are an OUTPUT ENRICHMENT — never a gate, never touch state.
docs:
  registry: "{project-root}/.sdlc/docs.json"         # project-wide docs/Pages connection (NOT per-epic)
  targets: [github-pages, gitlab-pages]               # supported Pages hosts; auto-detected from hub.platform
  scope: hub                                          # publish from: hub (default) | <repo-name> | dedicated
  degrade: build-only                                 # no Pages host / no gh|glab => build the dist, publish via CI
  login_gate: false                                   # public docs by default (the client-side gate is presentational only)
  manifest: "{project-root}/epics/EP-<slug>/.sdlc/docs-build.json"   # per-epic staleness baseline (yad-docs)
  overview: "{project-root}/docs/sdlc-site/"          # the project SDLC-overview site (yad-docs-overview)
  # Auth: `yad-connect-docs` records only how to reach the Pages host (platform + base path); it stores NO
  # tokens — publishing runs as the LOCAL user's own gh/glab or the platform CI. Idempotent, refreshable.
  auth: user

# Hub platform + front-half review bridge (yad-connect-repos `detect-hub`; yad-review-gate + yad-hub-bridge).
# The product hub is itself a git repo on a platform. With the bridge enabled, the front-half review/
# comment/approval cycle runs through a real PR/MR on the hub: a review PR is opened per artifact, reviewers
# approve/comment on the platform (their own gh/glab auth — NO stored tokens), and `yad-review-gate action: sync`
# pulls that state into the file ledger (approvals.json/reviews/*.md) and runs the UNCHANGED gate predicate.
# The file ledger stays the source of truth. Degrades to the file-only gate when there is no platform / no CLI.
hub:
  config: "{project-root}/.sdlc/hub.json"             # hub platform + reviewer roster (committed, like repos.json)
  pr_ledger: "{project-root}/epics/EP-<slug>/.sdlc/hub-prs.json"   # per-step review-PR record (sibling of approvals.json)
  bridge: true                                         # master enable; false => file-only front gates everywhere
  platforms: [github, gitlab]                          # detected from the hub's own `git remote get-url origin`
  artifact_branch: "review/EP-<slug>/<artifact-base>"  # branch a review PR is opened on, per artifact
  # roster (in hub.json) maps a platform login -> yad name + role; domain-owners are DERIVED from
  # repos.json (a roster name that equals a repo's domain_owner owns that repo's domain) — never duplicated.

# Phase 4 (automation) — the SECOND dial made real. Until Phase 4 nothing read this dial; the
# orchestrator (yad-run) now does. Governing rule: automation is EARNED per step, with trust-log
# evidence, and is reversible in one move (phase-4-build-plan.md §"one principle"). Front states are
# never listed in back_steps and can never be flipped — the engineer keeps authority over decisions.
automation:
  # The only steps that MAY be automated, safest-end first (phase-4-build-plan.md build order).
  # Phase 4a ships the engine + earns `checks` (Step B). `tasks`/`implement` advance are Phase 4b.
  back_steps: [spec, tasks, implement, checks]
  default: human_approve            # every back step starts manual; machine_advance must be earned
  # A back step is a CANDIDATE for machine_advance only once its trust-log slice clears this bar.
  # "It seems fine" is not evidence (phase-4-build-plan.md §"Explicitly NOT").
  trust_threshold:
    min_runs: 5                     # at least this many recorded runs at the step
    min_approved_unchanged: 0.8     # >= this fraction "approved-unchanged" over those runs
  # Hard lock — the dial-setter REFUSES machine_advance for these, regardless of trust evidence.
  # The front authoring steps (already locked:true in state.json; analysis is optional) + the human
  # merge gate.
  locked_steps: [discovery, analysis, epic, architecture, ui-design, stories, test-cases, engineer-review]
  # Kill switch (phase-4-build-plan.md §Safety): true => every step forced to human_approve
  # system-wide, no per-step edits. One line, instantly reversible. Toggle via `yad-run action: kill`.
  kill_switch: false

# Phase 6 (post-lock change management) — FEATURE THREADS. After the contract locks and code ships, a
# change must not MUTATE a locked artifact (that destroys the audit trail and the lock). Instead every
# change request becomes a NEW epic, threaded to its parent: a feature is a thread of linked epics
# (genesis -> change -> defect -> ...). A change-epic INHERITS unchanged front artifacts from its parent
# BY REFERENCE and only RE-AUTHORS what it changes — so artifacts are never stale, only superseded; the
# feature's current truth is the head of the thread, and the chain IS the evolution timeline. See
# docs/phase-6-build-plan.md and skills/yad-change. yad-defects/yad-timeline render it; yad-reconcile
# (read-only, like yad-docs-sync) flags orphan/drift; the three gates above enforce it.
change:
  # The kind of an epic (epic.md frontmatter `kind:`). `feature` is the genesis (default when absent).
  kinds: [feature, change, defect, hotfix]
  # Triage DEPTH (yad-change auto-proposes, human confirms) -> which front states are re-authored vs
  # inherited. defect-fix re-authors stories+test-cases only; contract-surface re-authors architecture
  # and RE-LOCKS (a new hash routes architecture-review through the contract escalation, as today).
  depths: [defect-fix, behavioral-no-surface, contract-surface, new-capability]
  thread_id: genesis_epic_id            # the thread id = the genesis epic's id (never renamed -> stablest anchor)
  # Lineage frontmatter added to epic.md (mirrors the design:/testing: enrichment blocks — the locked
  # state.json step shape is untouched). `thread` is a DERIVED cache (authoritative = walk `parent` to
  # the root); a mismatch is detectable corruption (yad doctor threadChecks). defect/hotfix also carry
  # origin/severity/escape_stage/root_cause for the quality report (yad-defects).
  lineage_frontmatter: [kind, thread, parent, inherits, supersedes, origin, severity, escape_stage, root_cause]
  artifact_bases: [epic, architecture, contract, ui-design, stories, test-cases]   # what `inherits` may list
  severity: [sev1, sev2, sev3, sev4]    # defect/hotfix severity
  # Per-change-epic ledgers (siblings of approvals.json; the locked state.json shape is untouched).
  ledgers:
    change: "{project-root}/epics/EP-<slug>/.sdlc/change.json"          # intake + triage (one per change/defect/hotfix epic)
    debt:   "{project-root}/epics/EP-<slug>/.sdlc/reconcile-debt.json"  # append-only hotfix ship-first debt
  # An inherited artifact is taken by REFERENCE: the change-epic's contract-lock.json is a POINTER-LOCK
  # carrying the parent's hash verbatim (+ inheritedFrom/ref), so contract-check.sh passes UNCHANGED and
  # the surface physically cannot drift (there is no contract.md in the child to edit). Omitting
  # `architecture` from `inherits` is what triggers a real re-lock + the architecture gate.
  inherit_by: reference
  # An epic is SEALED once every story is `shipped` (build.story_build_states). epic-open.sh refuses new
  # behaviour on a sealed epic -> the change must land in a new threaded change-epic (the front half is
  # forced to stay current; staleness is unshippable).
  seal_on: all-stories-shipped
  # Brownfield stub anchor (yad-stub): an already-built feature with NO epic can't be a change parent
  # (yad-change requires a real parent; lineage-check rejects a missing one). yad-stub mints the smallest
  # real thread anchor — a genesis epic.md (kind:feature, thread:self) marked `stub: backfill-pending` /
  # `verified: false`, with a state.json carrying `kind: stub` + `currentStep: backfill-pending`. A change
  # threaded off a stub inherits only what exists (boundHash: null for the undocumented surface bases) and
  # writes NO pointer-lock; change.json records `parentStub: true`. `yad-backfill promote` flips the stub
  # to verified (clears the marker), at which point contract protection begins for the thread.
  stub:
    marker: backfill-pending            # epic.md `stub:` value while the anchor is un-promoted
    state_kind: stub                    # state.json top-level `kind` + `currentStep: backfill-pending` sentinel
    allow_stub_parent: true             # a change/defect MAY thread off an un-promoted stub (default on)
    promote_by: yad-backfill            # `yad-backfill promote EP-<slug>` documents + flips the stub to real
  hotfix:
    ship_first: true                    # a hotfix may run the build half BEFORE its front gates approve
    debt_blocks_next_change: true       # but opens reconcile-debt.json; the next change on the thread is blocked until paid
    debt_requires: [artifacts-updated, regression-test]   # evidence that clears the debt
