You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by po...@apache.org on 2026/05/04 09:38:42 UTC

(airflow-steward) branch main updated: feat(setup-steward): multi-install + committed/local lock split + drift detection (#40)

This is an automated email from the ASF dual-hosted git repository.

potiuk pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow-steward.git


The following commit(s) were added to refs/heads/main by this push:
     new d58fec6  feat(setup-steward): multi-install + committed/local lock split + drift detection (#40)
d58fec6 is described below

commit d58fec6e62b7fafb3222bbf2c689ef4889c6c805
Author: Jarek Potiuk <ja...@potiuk.com>
AuthorDate: Mon May 4 11:38:35 2026 +0200

    feat(setup-steward): multi-install + committed/local lock split + drift detection (#40)
    
    Rewrite setup-steward to support three install methods, split
    the lock file into committed (project pin) and local
    (per-machine fetch), and drive every framework-skill invocation
    through a drift check that compares the two locks.
    
    Three install methods (copy-pasteable recipes in
    docs/setup/install-recipes.md)
    
    - svn-zip: signed zip from ASF dist
      (`https://dist.apache.org/repos/dist/release/<project>/`),
      the canonical ASF release distribution path. Verified via
      SHA-512 + OpenPGP. Recommended for production once the
      framework ships official releases.
    - git-tag: pinned to a specific git tag — frozen by tag.
    - git-branch: tracks a branch tip (default: main) — the WIP
      path during pre-release.
    
    Each recipe is a single shell block: adds the right gitignore
    entries, downloads + verifies + extracts, copies the
    setup-steward skill into the adopter's skills dir (matching the
    flat or double-symlinked convention), and tells the user to
    follow /setup-steward from there.
    
    Lock-file split
    
    - .apache-steward.lock (committed) — the project's pin: method,
      url, ref, plus a verification anchor (commit SHA for tag,
      sha512 for zip). Edited only by /setup-steward; bumping it is
      a deliberate project-level commit.
    - .apache-steward.local.lock (gitignored) — what THIS machine
      actually fetched: source_method, source_url, source_ref,
      fetched_commit, fetched_at. Per-developer, per-checkout.
    
    Drift detection
    
    Every framework skill (and /setup-steward verify) compares the
    local lock to the committed lock at the top of its run. On
    mismatch:
    
    - Method or URL differ → ✗ full re-install
    - Ref differs (project bumped tag, or git-branch local is
      behind upstream) → ⚠ sync
    - svn-zip SHA-512 differs from the committed anchor → ✗
      security-flagged; investigate before upgrading
    
    The proposal is always /setup-steward upgrade. On user OK,
    upgrade:
    
    1. Deletes the gitignored snapshot outright.
    2. Re-installs per the committed lock.
    3. Refreshes gitignored framework-skill symlinks (adds new
       skills, removes renamed-away ones).
    4. Reconciles agentic overrides against the new framework
       structure (surfaces conflicts; never auto-rewrites).
    5. Updates the local lock.
    
    Changes
    
    - Rewrite setup-steward/SKILL.md, adopt.md, upgrade.md,
      verify.md to express the new model.
    - New: docs/setup/install-recipes.md with the three
      copy-pasteable recipes + explanatory framing.
    - README's Adopting the framework section split into
      Bootstrap (shell) + Skill takeover + Subsequent
      contributors + Drift detection.
    
    Out of scope (follow-up): wire the drift check into each
    framework skill's pre-flight section (alongside the
    override-consultation hook from #39). 15 skills to touch;
    mechanical, deferred to keep this PR focused on the
    mechanism.
    
    Generated-by: Claude Code (Claude Opus 4.7)
---
 .claude/skills/setup-steward/SKILL.md   | 239 +++++++++++++-------
 .claude/skills/setup-steward/adopt.md   | 377 +++++++++++++++++++-------------
 .claude/skills/setup-steward/upgrade.md | 254 +++++++++++++--------
 .claude/skills/setup-steward/verify.md  | 166 ++++++++------
 README.md                               |  99 +++++++--
 docs/setup/install-recipes.md           | 247 +++++++++++++++++++++
 6 files changed, 972 insertions(+), 410 deletions(-)

diff --git a/.claude/skills/setup-steward/SKILL.md b/.claude/skills/setup-steward/SKILL.md
index 12c5952..75dc65f 100644
--- a/.claude/skills/setup-steward/SKILL.md
+++ b/.claude/skills/setup-steward/SKILL.md
@@ -8,19 +8,22 @@ description: |
   gitignored snapshot this skill manages. Sub-actions:
     `/setup-steward`         — first-time adoption (default)
     `/setup-steward upgrade` — refresh the gitignored snapshot
-    `/setup-steward verify`  — health check the integration
+                                per the committed lock
+    `/setup-steward verify`  — health check + drift detection
     `/setup-steward override <skill>` — open or scaffold an
                                agentic override for a framework
                                skill in `.apache-steward-overrides/`
 when_to_use: |
   Invoke when the user says "adopt apache-steward", "adopt
-  apache/airflow-steward", "set up steward in this repo", or
-  the agent equivalent triggered by following the framework's
-  README adoption instructions. Also for periodic maintenance:
-  "upgrade steward", "verify steward setup", "update the
-  steward snapshot". This is the only framework skill that
-  should be **copied** into an adopter's repo (every other
-  framework skill is a symlink the adopt sub-action wires up).
+  apache/airflow-steward", "set up steward in this repo",
+  "follow .claude/skills/setup-steward", or the agent
+  equivalent triggered by following the framework's README
+  adoption instructions. Also for periodic maintenance:
+  "upgrade steward", "verify steward setup", "check steward
+  drift", "the snapshot is stale". This is the only framework
+  skill that should be **copied** into an adopter's repo
+  (every other framework skill is a symlink the adopt
+  sub-action wires up).
 license: Apache-2.0
 ---
 
@@ -30,11 +33,12 @@ license: Apache-2.0
 <!-- Placeholder convention (see ../../AGENTS.md#placeholder-convention-used-in-skill-files):
      <project-config>           → adopter's `.apache-steward-overrides/` directory
      <snapshot-dir>             → `.apache-steward/` (gitignored snapshot of the framework)
+     <committed-lock>           → `.apache-steward.lock` (committed — project's pin)
+     <local-lock>               → `.apache-steward.local.lock` (gitignored — per-machine record)
      <upstream>                 → adopter's public source repo (the repo this skill is being run in)
      <framework-source>         → the apache-steward source we download a snapshot from
-                                   (currently `https://github.com/apache/airflow-steward.git`,
-                                    later `https://downloads.apache.org/airflow/...` (e.g.) once
-                                    official ASF releases ship). -->
+                                   — one of: signed zip from ASF dist, git tag, git branch.
+                                   See [`docs/setup/install-recipes.md`](../../../docs/setup/install-recipes.md). -->
 
 # setup-steward
 
@@ -43,13 +47,26 @@ project commits**. Every other apache-steward skill (security,
 pr-management) is a gitignored symlink into the gitignored
 snapshot at `<snapshot-dir>` that this skill manages.
 
-The adoption model is **snapshot + agentic overrides** (not
-submodule, not marketplace, not vendored copy):
+The adoption model is **snapshot + agentic overrides + drift-
+aware updates** (not submodule, not marketplace, not vendored
+copy):
 
-- The framework is downloaded as a `--depth=1` git checkout (or,
-  once official ASF releases ship, a signed tarball) into
-  `<snapshot-dir>` and **gitignored** in the adopter repo. The
-  snapshot is a build artefact, not source.
+- The framework is downloaded into `<snapshot-dir>` and
+  **gitignored** in the adopter repo. The snapshot is a build
+  artefact, not source.
+- Three install methods are supported (see
+  [`docs/setup/install-recipes.md`](../../../docs/setup/install-recipes.md)
+  for verbatim copy-pasteable recipes):
+  - **svn-zip** — released, signed zip from ASF distribution
+    (recommended for production once releases ship).
+  - **git-tag** — pinned to a specific git tag.
+  - **git-branch** — tracks a branch tip (default: `main`,
+    the WIP path).
+- **Two lock files** record the framework version. The
+  committed one declares what the project pins to; the local
+  one records what each machine actually fetched. Drift
+  between them is surfaced and remediated by
+  `/setup-steward upgrade`.
 - Symlinks from the adopter's skill directory into
   `<snapshot-dir>/.claude/skills/<framework-skill>/` make the
   framework's skills callable as if they lived in the adopter
@@ -65,14 +82,68 @@ submodule, not marketplace, not vendored copy):
   [`docs/setup/agentic-overrides.md`](../../../docs/setup/agentic-overrides.md)
   for the design rationale.
 
+## The two lock files
+
+The framework's lock-file model splits **what the project pins
+to** (committed) from **what this machine actually fetched**
+(local). This split is the foundation of drift detection and
+the multi-installer support.
+
+### `<committed-lock>` — `.apache-steward.lock`
+
+Committed at the adopter repo root. The **project's pin**.
+Edited only by `/setup-steward`; do not modify by hand.
+
+```text
+# .apache-steward.lock — committed; the project's pin.
+
+method: <git-branch | git-tag | svn-zip>
+url:    <see per-method format below>
+
+# For method=git-branch:
+ref:    main
+
+# For method=git-tag:
+ref:    v1.0.0          # the tag name
+commit: <SHA>           # the commit the tag pointed to when committed
+
+# For method=svn-zip:
+ref:    1.0.0           # the version number
+sha512: <hash>          # the released zip's SHA-512 (for re-fetch verification)
+```
+
+The next adopter who runs `/setup-steward adopt` reads this
+file and re-installs to the **same version** the project
+declared. This is the core of the "adopt once, all subsequent
+users get the same thing" promise.
+
+### `<local-lock>` — `.apache-steward.local.lock`
+
+Gitignored at the adopter repo root. The **local snapshot's
+fingerprint**. Records what this machine fetched and when.
+
+```text
+# .apache-steward.local.lock — gitignored; per-machine.
+
+source_method:    <git-branch | git-tag | svn-zip>
+source_url:       <URL the snapshot was actually fetched from>
+source_ref:       <branch / tag / version actually fetched>
+fetched_commit:   <commit SHA on disk now>
+fetched_at:       <ISO-8601 timestamp>
+```
+
+The drift check on every framework-skill invocation compares
+this against `<committed-lock>` and surfaces any mismatch as a
+proposed `/setup-steward upgrade`.
+
 ## Detail files in this directory
 
 | File | Purpose |
 |---|---|
-| [`adopt.md`](adopt.md) | First-time adoption walk-through — detect the adopter's skills-dir convention, download the snapshot, set up `.gitignore`, create the framework-skill symlinks, scaffold `.apache-steward-overrides/`, update the adopter's project docs. The default sub-action. |
-| [`upgrade.md`](upgrade.md) | Refresh the gitignored snapshot to a newer framework version, reconcile any agentic overrides against the new framework structure, surface conflicts. |
-| [`verify.md`](verify.md) | Read-only health check — snapshot present + intact, symlinks point at live targets, `.gitignore` correct, `.apache-steward-overrides/` exists, the `setup-steward` skill itself is current. |
-| [`conventions.md`](conventions.md) | Adopter skills-dir convention auto-detection — flat `.claude/skills/<name>/`, the `.claude/skills/<name>` → `.github/skills/<name>/` double-symlink pattern (e.g. apache/airflow), or neither yet. |
+| [`adopt.md`](adopt.md) | First-time adoption walk-through — recognise existing-snapshot vs needs-bootstrap, write the two lock files, ask the user which skill families to wire up, create the gitignored symlinks, scaffold `.apache-steward-overrides/`, install the post-checkout hook, update project docs. The default sub-action. |
+| [`upgrade.md`](upgrade.md) | Refresh the gitignored snapshot per the committed lock, reconcile any agentic overrides + symlinks against the new framework structure, surface conflicts. Drives the on-drift remediation flow. |
+| [`verify.md`](verify.md) | Read-only health check — snapshot present + intact, both lock files in sync, symlinks point at live targets, `.gitignore` correct, `.apache-steward-overrides/` exists, drift status (committed vs local), the `setup-steward` skill itself is current. |
+| [`conventions.md`](conventions.md) | Adopter skills-dir convention auto-detection — flat `.claude/skills/<n>/`, the `.claude/skills/<n>` → `.github/skills/<n>/` double-symlink pattern (e.g. apache/airflow), or neither yet. |
 | [`overrides.md`](overrides.md) | Agentic-override file management — open / scaffold an override for a framework skill, list existing overrides, help reconcile when the framework changes the underlying skill's structure on upgrade. |
 
 ## Golden rules
@@ -83,54 +154,72 @@ only** from an adopter's perspective. Every modification an
 adopter wants must go into `.apache-steward-overrides/` (where
 it is *committed* and survives the next `upgrade`). The skill,
 and any other framework skill consulting overrides at run-time,
-**never** writes to `<snapshot-dir>`. If the user wants to
-upstream a framework change, the agent reads the latest
-`apache/airflow-steward` `main`, implements the change there,
-and opens a PR against the framework repo.
+**never** writes to `<snapshot-dir>`.
+
+**Golden rule 2 — `<committed-lock>` is the project's pin;
+`<local-lock>` is per-machine truth.** They serve different
+purposes and live in different places:
+
+- `<committed-lock>` declares what version the *project* uses.
+  Edited by the adopter who runs `/setup-steward adopt` first
+  (or who later runs `/setup-steward upgrade` and accepts the
+  new pin). Bumping it is a deliberate project-level action;
+  the bump shows up in the `git diff` of the PR that proposed
+  it.
+- `<local-lock>` records what *this machine* installed. Updated
+  silently by `/setup-steward adopt` and `/setup-steward
+  upgrade`. Per-developer, per-checkout, per-worktree.
+
+**Golden rule 3 — drift surfaces, drift gets remediated.**
+Every framework skill (and `/setup-steward verify`) checks
+`<committed-lock>` vs `<local-lock>` at the top of its run.
+On mismatch the skill surfaces the gap and proposes
+`/setup-steward upgrade`. The user accepts or defers; if they
+accept, `upgrade`:
 
-**Golden rule 2 — `.gitignore` keeps the adopter repo clean.**
+1. Deletes `<snapshot-dir>` outright.
+2. Re-installs per the *committed* lock (the new version the
+   project chose).
+3. Refreshes the gitignored framework-skill symlinks — adds
+   any new framework skills the user's family pick covers,
+   removes any framework skills that were renamed away or
+   removed.
+4. Reconciles agentic overrides against the new framework
+   structure (surfaces conflicts; never auto-rewrites).
+5. Updates `<local-lock>` to the new fetch.
+
+**Golden rule 4 — `.gitignore` keeps the adopter repo clean.**
 Three things gitignored in the adopter repo:
 
-- `<snapshot-dir>` (the entire framework snapshot)
-- the symlinks `setup-steward adopt` creates in the adopter's
+- `<snapshot-dir>` (the entire framework snapshot — gigabytes
+  potentially).
+- `<local-lock>` (per-machine state).
+- The symlinks `setup-steward adopt` creates in the adopter's
   skills directory (they target the gitignored snapshot, so
-  they would dangle in a fresh clone)
-- the adopter's own scratch artefacts that other framework
-  skills might create (`/tmp/...` style state caches)
+  they would dangle in a fresh clone).
 
 **Committed**: this skill (`setup-steward`), the
-`.apache-steward-overrides/` directory, the `.gitignore`
-entries themselves, any project-doc updates the `adopt`
-sub-action makes.
+`<committed-lock>`, the `.apache-steward-overrides/`
+directory, the `.gitignore` entries themselves, any
+project-doc updates the `adopt` sub-action makes.
 
-**Golden rule 3 — follow the adopter's existing skills-dir
+**Golden rule 5 — follow the adopter's existing skills-dir
 convention.** Different ASF projects already organise their
 `.claude/skills/` differently (see
-[`conventions.md`](conventions.md)):
-
-- **flat**: `.claude/skills/<name>/SKILL.md` — directly in the
-  Claude Code-discovered location.
-- **double-symlinked** (e.g. apache/airflow today): the actual skill
-  content lives under `.github/skills/<name>/` and
-  `.claude/skills/<name>` is a symlink into it. Claude Code
-  discovers via `.claude/skills/`; the user maintains under
-  `.github/skills/`.
-
-The `adopt` sub-action detects which pattern is in place and
-matches it. **The framework's symlinks land at the same depth
-as the adopter's existing skills**, not one level off.
+[`conventions.md`](conventions.md)). The `adopt` sub-action
+detects which pattern is in place and matches it.
 
-**Golden rule 4 — copy this skill, symlink the rest.** This
+**Golden rule 6 — copy this skill, symlink the rest.** This
 skill (`setup-steward`) is the **only** framework skill that
 gets copied into an adopter repo. All other framework skills
-(`security-issue-import`, `pr-management-triage`, etc.) are
-symlinked into the gitignored snapshot. Mixing the two — for
-example, copying a security skill — creates a maintenance
-hazard: copies drift from the framework's source-of-truth, and
-agentic overrides (which assume the framework version is the
-one in the snapshot) silently mis-apply.
-
-**Golden rule 5 — agentic overrides are read at run-time.**
+are symlinked into the gitignored snapshot. Mixing the two —
+copying a security skill, for instance — creates a
+maintenance hazard: copies drift from the framework's source-
+of-truth, and the drift-detection mechanism (which assumes
+the framework version is the one in `<snapshot-dir>`)
+silently mis-applies.
+
+**Golden rule 7 — agentic overrides are read at run-time.**
 Every framework skill that supports overrides starts its run
 by checking `.apache-steward-overrides/<this-skill>.md` for
 adopter-specific instructions and applying them before
@@ -146,26 +235,24 @@ The skill dispatches by the first positional argument:
 
 | Invocation | Loads | Purpose |
 |---|---|---|
-| `/setup-steward` (no args) | [`adopt.md`](adopt.md) | First-time adoption (default). |
+| `/setup-steward` (no args) | [`adopt.md`](adopt.md) | First-time adoption (default). Idempotent — re-running on an already-adopted repo behaves like `verify`. |
 | `/setup-steward adopt` | [`adopt.md`](adopt.md) | Same as no-arg — explicit form. |
-| `/setup-steward upgrade` | [`upgrade.md`](upgrade.md) | Refresh snapshot + reconcile overrides. |
-| `/setup-steward verify` | [`verify.md`](verify.md) | Read-only health check. |
+| `/setup-steward upgrade` | [`upgrade.md`](upgrade.md) | Refresh snapshot per `<committed-lock>` + reconcile overrides + refresh symlinks. |
+| `/setup-steward verify` | [`verify.md`](verify.md) | Read-only health check + drift status report. |
 | `/setup-steward override <skill>` | [`overrides.md`](overrides.md) | Open / scaffold an override file. |
 
-If the snapshot is missing (no `<snapshot-dir>/`), the skill
-treats that as `adopt` regardless of which sub-action was
-named — the user has invoked on a repo that has not yet been
-adopted, and the right next step is to walk through adoption.
+If the snapshot is missing (no `<snapshot-dir>/`) and
+`<committed-lock>` exists, the skill treats any sub-action as
+the recover-snapshot path: re-install per the committed lock
+first, then continue.
 
 ## Inputs
 
-The skill is mostly driven by detection (it reads the adopter
-repo's state) but accepts these optional flags:
-
 | Flag | Effect |
 |---|---|
-| `from:<git-ref>` | Adopt / upgrade from a specific framework `<git-ref>` (branch, tag, or commit SHA) instead of `main`. Useful for testing a framework PR locally before it merges. |
-| `skill-families:<list>` | Comma-separated list of skill families to symlink (`security`, `pr-management`). Default on adopt: prompt the user. Default on upgrade: re-symlink the families currently linked. |
+| `from:<git-ref>` / `from:<version>` | Adopt or upgrade from a specific framework ref or version. Used during `adopt` (overrides the user prompt) and `upgrade` (overrides the committed lock for *this run only* — does NOT update the committed lock). |
+| `method:<git-branch\|git-tag\|svn-zip>` | Pick the install method explicitly. Default during `adopt`: prompt the user. |
+| `skill-families:<list>` | Comma-separated families to symlink (`security`, `pr-management`). Default on `adopt`: prompt. Default on `upgrade`: re-symlink the families currently linked. |
 | `dry-run` | Show what the skill would do without writing anything. |
 
 ## What this skill is NOT for
@@ -173,12 +260,8 @@ repo's state) but accepts these optional flags:
 - Not for installing the secure agent setup (sandbox, hooks,
   pinned tools). That is
   [`setup-isolated-setup-install`](../setup-isolated-setup-install/SKILL.md).
-  The two are independent: an adopter can have steward set up
-  but no isolated-setup wired (run setup-isolated-setup-install
-  to fix), or have isolated-setup wired against a stale
-  snapshot (run `setup-steward upgrade`).
 - Not for upgrading framework tools installed on the host
-  (`bubblewrap`, `socat`, `claude-code` itself). Those go via
+  (`bubblewrap`, `socat`, `claude-code` itself). That is
   [`setup-isolated-setup-update`](../setup-isolated-setup-update/SKILL.md).
 - Not for syncing the user's `~/.claude-config` across
   machines. That is
@@ -191,7 +274,7 @@ repo's state) but accepts these optional flags:
 
 | Symptom | Likely cause | Remediation |
 |---|---|---|
-| `/setup-steward verify` reports the snapshot present but the symlinks dangle | adopter ran a `git clone` but not `/setup-steward` after — symlinks are gitignored but persist in their target's absence | run `/setup-steward adopt` (it idempotently re-creates symlinks) |
-| `/setup-steward upgrade` surfaces conflicts in `.apache-steward-overrides/<skill>.md` | the framework restructured the skill in a way that invalidates an existing override | open the override file, follow the conflict markers, or invoke `/setup-steward override <skill>` to re-scaffold |
-| Worktree off the adopter repo can't find framework skills | worktrees off the adopter don't auto-inherit the gitignored snapshot | the `adopt` sub-action installs a `post-checkout` git hook that re-runs the snapshot install on worktree creation; verify the hook is present (`/setup-steward verify`) |
-| `git clone` of an upstream PR sees no framework skills | expected — the snapshot is gitignored, so a fresh clone has no `<snapshot-dir>`. The clone needs `/setup-steward` once before any framework skill is invocable | run `/setup-steward` from the cloned repo |
+| `/setup-steward verify` reports drift between committed and local locks | Project lead bumped `<committed-lock>` since this machine last fetched, or local snapshot is stale on a `main`-tracking adopter | `/setup-steward upgrade` |
+| Snapshot present but symlinks dangle | Adopter ran `git clone` but not `/setup-steward` after — symlinks are gitignored but persist in their target's absence on disk | `/setup-steward verify --auto-fix-symlinks` (or `/setup-steward adopt`, idempotent) |
+| Worktree off the adopter repo can't find framework skills | Worktrees off the adopter don't auto-inherit the gitignored snapshot | The `adopt` sub-action installs a `post-checkout` git hook that re-runs the snapshot install on worktree creation; verify the hook is present (`/setup-steward verify`) |
+| `git clone` of an upstream PR sees no framework skills | Expected — the snapshot is gitignored, so a fresh clone has no `<snapshot-dir>`. The clone needs `/setup-steward` once before any framework skill is invocable | `/setup-steward` |
diff --git a/.claude/skills/setup-steward/adopt.md b/.claude/skills/setup-steward/adopt.md
index ddb5c24..b1680e5 100644
--- a/.claude/skills/setup-steward/adopt.md
+++ b/.claude/skills/setup-steward/adopt.md
@@ -4,240 +4,303 @@
 # adopt — first-time install of apache-steward into an adopter repo
 
 The default sub-action when the user says "adopt apache-steward".
-Walks through detection, snapshot install, and the small set of
-adopter-side artefacts that need to land on disk.
+
+There are two adoption shapes the skill recognises and routes
+between automatically:
+
+- **Fresh adoption (no committed lock yet).** The first
+  adopter on a project. Runs the full bootstrap: pick the
+  install method, fetch the snapshot, write *both* lock
+  files, wire up symlinks, scaffold overrides, install
+  hooks, update docs.
+- **Subsequent adoption (committed lock exists).** A new
+  developer joining a project that already adopted. Reads
+  `<committed-lock>` to know what to install, fetches per
+  that pin, writes only the `<local-lock>`, refreshes
+  symlinks. Skips the doc-update + interactive-prompt flow.
+
+> **Note on the bootstrap recipe.** `setup-steward` is **the
+> only framework artefact an adopter commits**. Getting it
+> *into* a fresh adopter repo is the chicken-and-egg the
+> [install-recipes](../../../docs/setup/install-recipes.md)
+> doc resolves: copy-pasteable shell recipes per install
+> method that fetch the snapshot + place the `setup-steward`
+> skill content + add `.gitignore` entries. Once that
+> recipe runs and `setup-steward` is on disk, the agent
+> follows this file to finish adoption.
 
 ## Inputs
 
-- `from:<git-ref>` — adopt from a specific framework `<git-ref>`
-  (default: `main` of `apache/airflow-steward`).
-- `skill-families:<list>` — comma-separated families to symlink
-  in (`security`, `pr-management`). Default: prompt the user.
+- `from:<git-ref>` / `from:<version>` — explicit ref or
+  version (overrides the prompt).
+- `method:<git-branch | git-tag | svn-zip>` — explicit method
+  (overrides the prompt).
+- `skill-families:<list>` — comma-separated families to
+  symlink (default: prompt).
 
 ## Step 0 — Pre-flight
 
-1. Confirm we are in a git repo (`git rev-parse --show-toplevel`).
-   If not, surface and stop — the user opened the agent in the
-   wrong directory.
+1. Confirm we are in a git repo (`git rev-parse
+   --show-toplevel`).
 2. Confirm we are **not** in `apache/airflow-steward` itself
-   (read `git remote get-url origin` and refuse if it resolves
-   to the framework). Adopting the framework into itself is a
-   no-op the user did not intend.
+   (read `git remote get-url origin` and refuse if it
+   resolves to the framework).
 3. Detect the adopter's existing skills-dir convention by
-   following [`conventions.md`](conventions.md). The result
-   pins which directory the framework symlinks land in
-   (`<adopter-skills-dir>` from here on).
-
-## Step 1 — Pick the skill families
-
-If `skill-families:` was passed on the invocation, use those
-verbatim. Otherwise, present the families to the user and let
-them choose:
-
-- **`security`** — eight skills for security-issue handling
-  (`security-issue-import`, `security-issue-sync`,
-  `security-cve-allocate`, `security-issue-fix`, etc.).
-  Maintainer-only; not useful unless the project has a
-  security tracker.
-- **`pr-management`** — three skills for maintainer-facing PR
-  queue work (`pr-management-triage`,
-  `pr-management-stats`, `pr-management-code-review`).
-- **`setup`** *(implicit)* — the `setup-isolated-setup-*`,
-  `setup-steward-*`, `setup-shared-config-sync` skills. The
-  `setup` family is always installed because the snapshot
-  carries it; the symlinks are wired up regardless of the
-  user's other family picks.
-
-Show the user a short description of each family and ask which
-to install. Default to whichever family the user named in
-their initial "adopt" request (e.g. *"adopt apache-steward for
-PR triage"* → `pr-management`).
-
-## Step 2 — Download the snapshot
-
-Place the snapshot at `<repo-root>/.apache-steward/`. Use the
-WIP path for now (a `--depth=1` git checkout of the framework's
-`main` branch). The signed-tarball path
-(e.g. `https://downloads.apache.org/airflow/...` once ASF official
-releases ship per
-[release-policy](https://www.apache.org/legal/release-policy.html))
-is a future upgrade; both paths produce the same on-disk
-shape.
-
-```bash
-# WIP path — works today
-git clone --depth=1 \
-  --branch <git-ref-or-main> \
-  https://github.com/apache/airflow-steward.git \
-  .apache-steward
+   following [`conventions.md`](conventions.md). Pin the
+   result as `<adopter-skills-dir>` for the rest of this
+   flow.
+
+## Step 1 — Detect adoption shape
+
+```text
+if .apache-steward.lock exists:
+    → SUBSEQUENT adoption
+elif .apache-steward/ exists (snapshot only):
+    → manual recipe was run; finish bootstrap (write committed
+      lock from the recipe's choices, then continue as FRESH
+      from Step 5)
+else:
+    → FRESH adoption
 ```
 
-If `<repo-root>/.apache-steward/` already exists with content,
-the user is in upgrade territory — refuse and suggest
-`/setup-steward upgrade` instead. (Idempotent re-run after a
-*partial* adopt is fine — see Step 6.)
+## Step 2 — Pick install method (FRESH only)
+
+If the user passed `method:` and `from:` flags, use those
+verbatim. Otherwise, prompt:
+
+| Method | When | Reproducibility |
+|---|---|---|
+| `svn-zip` | Production once ASF releases ship to dist | Frozen by version |
+| `git-tag` | Pin a specific tag | Frozen by tag |
+| `git-branch` | Track a branch tip (default: `main`) | Tracks tip — best during pre-release |
+
+The verbatim shell that fetches per each method is in
+[`docs/setup/install-recipes.md`](../../../docs/setup/install-recipes.md).
+The skill at this point can either:
+
+- Tell the user "your manual recipe already ran — please
+  confirm the method you used, I will record it in the
+  committed lock", or
+- Run the per-method fetch itself if `<snapshot-dir>` does
+  not yet exist.
 
-Pin the snapshot version into a small `.apache-steward.lock`
-file at the repo root (committed) — record the source URL,
-the resolved commit SHA, and the date. The `verify` and
-`upgrade` sub-actions read this file.
+For a SUBSEQUENT adoption (committed lock present), skip the
+prompt entirely — re-use the method/url/ref from the
+committed lock.
+
+## Step 3 — Fetch the snapshot (if not already on disk)
+
+Per the chosen method (FRESH) or per the committed lock
+(SUBSEQUENT):
+
+- **`git-branch`**: `git clone --depth=1 --branch <ref> <url>
+  .apache-steward`
+- **`git-tag`**: `git clone --depth=1 --branch <tag> <url>
+  .apache-steward`. After clone, capture the resolved commit
+  SHA for `<committed-lock>` (FRESH only).
+- **`svn-zip`**: `curl` the zip + `.sha512` + `.asc`,
+  verify, `unzip` to `.apache-steward/`. Re-fetch
+  verification details into `<committed-lock>` (FRESH only).
+
+If `<snapshot-dir>/` already exists with content, skip the
+fetch — the recipe ran first and left the snapshot in place.
+
+After the fetch (or skip), confirm
+`<snapshot-dir>/.claude/skills/` lists the framework skills
+(`pr-management-*`, `security-*`, `setup-*`). If not, the
+fetch produced an unexpected layout — surface and stop.
+
+## Step 4 — Write `<committed-lock>` (FRESH only)
+
+Create `<repo-root>/.apache-steward.lock`:
 
 ```text
-# .apache-steward.lock (committed)
-source: https://github.com/apache/airflow-steward.git
-ref: main
-commit: <SHA>
-fetched: <ISO-8601 date>
+# .apache-steward.lock — committed; the project's pin.
+# Edited only by /setup-steward; do not modify by hand.
+
+method: <method>
+url:    <url>
+
+# Per-method fields:
+ref:    <branch | tag | version>
+# git-tag: also `commit: <SHA>`
+# svn-zip: also `sha512: <hash>`
 ```
 
-## Step 3 — `.gitignore` entries
+## Step 5 — Pick the skill families
+
+(SUBSEQUENT adoption: re-use the families currently
+symlinked, if any. Or re-prompt if none.)
+
+If `skill-families:` was passed, use those. Otherwise,
+prompt the user:
+
+- **`security`** — eight skills for security-issue
+  handling. Maintainer-only; not useful unless the project
+  has a security tracker.
+- **`pr-management`** — three skills for maintainer-facing
+  PR queue work.
+- **`setup`** *(implicit)* — always installed because the
+  snapshot carries it.
 
-Add (if not already present) to `<repo-root>/.gitignore`:
+Default to whichever family the user named in their
+initial "adopt" request (e.g. *"adopt apache-steward for PR
+triage"* → `pr-management`).
+
+## Step 6 — Write `<local-lock>`
+
+Always written, both FRESH and SUBSEQUENT. Records what
+this machine fetched.
 
 ```text
-# apache-steward — gitignored snapshot of the framework, refreshed
-# by the setup-steward skill. The snapshot is a build artefact, not
-# source. To re-create: /setup-steward (in your agent of choice).
-/.apache-steward/
+# .apache-steward.local.lock — gitignored; per-machine.
 
-# Symlinks the setup-steward skill creates into the snapshot. They
-# would dangle on a fresh clone before /setup-steward is run.
+source_method:    <method>
+source_url:       <url>
+source_ref:       <ref>
+fetched_commit:   <commit SHA — for git-branch and git-tag>
+fetched_at:       <ISO-8601 timestamp>
+```
+
+## Step 7 — `.gitignore` entries (FRESH only)
+
+The bootstrap recipe wrote these already; this step is
+idempotent — re-add them if they're missing.
+
+```text
+/.apache-steward/
+/.apache-steward.local.lock
 /.claude/skills/security-*
 /.claude/skills/pr-management-*
 /.claude/skills/setup-isolated-setup-*
-/.claude/skills/setup-steward-*
 /.claude/skills/setup-shared-config-sync
-# ...mirror the same patterns under .github/skills/ if the adopter
-# uses the double-symlinked convention (see conventions.md).
+/.github/skills/security-*
+/.github/skills/pr-management-*
+/.github/skills/setup-isolated-setup-*
+/.github/skills/setup-shared-config-sync
 ```
 
-Show the diff to the user before writing. The `setup-steward`
-skill itself (`*/setup-steward/`) is **not** gitignored — it
-is committed.
+Mirror under `.github/skills/` only if the adopter uses the
+double-symlinked convention.
 
-## Step 4 — Wire up the framework-skill symlinks
+## Step 8 — Wire up the framework-skill symlinks
 
-For each skill family the user picked plus the `setup` family,
-walk the snapshot's `.apache-steward/.claude/skills/` and create
-a gitignored symlink for every matching skill at
+For each skill family the user picked, walk
+`<snapshot-dir>/.claude/skills/` and create a gitignored
+symlink for every matching skill at
 `<adopter-skills-dir>/<skill>` → relative path into
-`.apache-steward/.claude/skills/<skill>/`.
+`<snapshot-dir>/.claude/skills/<skill>/`.
 
 If the adopter uses the double-symlinked convention
-(`.claude/skills/<n>` → `.github/skills/<n>/` per
-[`conventions.md`](conventions.md)), create both layers — the
-inner one in `.github/skills/` points at the snapshot, the
-outer `.claude/skills/` points at the inner.
+(see [`conventions.md`](conventions.md)), create both
+layers — the inner one in `.github/skills/` points at the
+snapshot, the outer `.claude/skills/` points at the
+inner. Both gitignored.
 
-**Never overwrite an existing committed skill** of the same name.
-If the adopter repo already has e.g. `.github/skills/pr-triage`
-(an old-name in-repo copy), surface the conflict and stop. The
-user resolves manually — likely by deleting the stale copy and
-re-running.
+**Never overwrite an existing committed skill** of the same
+name. Surface conflicts and stop.
 
-Show the symlinks the skill is about to create, ask the user
-to confirm, then create them.
+Show the symlinks the skill is about to create, ask the
+user to confirm, then create them.
 
-## Step 5 — Scaffold `.apache-steward-overrides/`
+## Step 9 — Scaffold `.apache-steward-overrides/` (FRESH only)
 
-Create `<repo-root>/.apache-steward-overrides/` (directory) if
-it doesn't exist, with a small `README.md` inside that explains
-the contract:
+Create `<repo-root>/.apache-steward-overrides/` (directory)
+with a small `README.md` inside:
 
 ```markdown
 # apache-steward overrides
 
-Agent-readable instructions that **override** specific steps or
-behaviours of the apache-steward framework's skills, scoped to
+Agent-readable instructions that override specific steps or
+behaviours of apache-steward framework skills, scoped to
 this adopter repo. Each override file is named after the
 framework skill it modifies (e.g. `pr-management-triage.md`
 overrides the `pr-management-triage` skill).
 
-The framework skills consult this directory at run-time before
-executing default behaviour. See
+The framework skills consult this directory at run-time
+before executing default behaviour. See
 [`docs/setup/agentic-overrides.md`](https://github.com/apache/airflow-steward/blob/main/docs/setup/agentic-overrides.md)
 in the framework for the full contract.
 
 **Hard rule**: never modify the snapshot under
-`<repo-root>/.apache-steward/`. Local mods go here. Framework
-changes go via PR to `apache/airflow-steward`.
+`<repo-root>/.apache-steward/`. Local mods go here.
+Framework changes go via PR to `apache/airflow-steward`.
 ```
 
-This directory is **committed** (the whole point is for
-overrides to ship with the adopter repo).
+This directory is **committed** (overrides ship with the
+adopter repo).
 
-## Step 6 — Worktree-aware post-checkout hook
+## Step 10 — Worktree-aware post-checkout hook (FRESH only)
 
-Install a `post-checkout` git hook at
+Install
 `<repo-root>/.git/hooks/post-checkout` that re-creates the
-gitignored symlinks if a fresh worktree is checked out off
-this repo. (The snapshot itself is gitignored and won't follow
-the worktree, but the hook keeps the symlink shape consistent.)
-
-The hook is a one-liner that re-invokes
-`/setup-steward verify --auto-fix-symlinks` against the new
-worktree path.
-
-Surface the hook content to the user before writing.
+gitignored symlinks if a fresh worktree is checked out. The
+hook is a one-liner that re-invokes
+`/setup-steward verify --auto-fix-symlinks`. Surface the
+hook content to the user before writing.
 
-## Step 7 — Project doc updates
+## Step 11 — Project doc updates (FRESH only)
 
-Add (or extend) a brief paragraph in the adopter's `README.md`
-or `CONTRIBUTING.md` (whichever already mentions agents /
-skills) noting that this repo adopts apache-steward via the
-snapshot mechanism, and pointing at:
+Add (or extend) a brief paragraph in the adopter's
+`README.md` or `CONTRIBUTING.md` (whichever already mentions
+agents / skills) noting:
 
-- [`apache-steward`'s top-level README](https://github.com/apache/airflow-steward) for the framework's overview;
-- the local `.apache-steward-overrides/` for adopter-specific
-  modifications.
+- the project adopts apache-steward via the snapshot
+  mechanism;
+- a fresh clone needs `/setup-steward` to populate the
+  framework before any framework skill is invocable;
+- adopter-specific modifications live in
+  `.apache-steward-overrides/`.
 
 Surface the doc diff to the user before writing.
 
-## Step 8 — Sanity check
+## Step 12 — Sanity check
 
-Run [`verify.md`](verify.md)'s checklist as a final step. Every
-check should be ✓ before the skill reports success.
+Run [`verify.md`](verify.md)'s checklist as a final step.
+Every check should be ✓ before the skill reports success.
 
 ## Output to the user
 
 A summary of what was written:
 
 ```text
-✓ Snapshot installed at .apache-steward/ (commit <SHA>)
-✓ .gitignore updated (.apache-steward/, .claude/skills/security-*, ...)
-✓ Symlinks created:
-  .claude/skills/security-issue-import → .apache-steward/.claude/skills/security-issue-import/
-  .claude/skills/security-issue-sync → ...
-  ...
-✓ .apache-steward-overrides/ scaffold created (committed)
+✓ Method:   <method>
+✓ Source:   <url>@<ref>
+✓ Snapshot: .apache-steward/ (commit <SHA>)
+✓ Locks:    .apache-steward.lock (committed) + .apache-steward.local.lock (gitignored)
+✓ Symlinks: <list of created symlinks>
+✓ Overrides scaffold: .apache-steward-overrides/ (committed)
 ✓ post-checkout hook installed
-✓ README.md updated with adoption note
+✓ <repo>/README.md updated with adoption note
 
 Committed (you'll see in `git status`):
   .gitignore
   .apache-steward.lock
   .apache-steward-overrides/README.md
-  .claude/skills/setup-steward/   (this skill itself)
+  <adopter-skills-dir>/setup-steward/   (this skill itself)
   README.md (or CONTRIBUTING.md)
 
 Gitignored (do NOT commit):
   .apache-steward/
-  .claude/skills/security-*
-  .claude/skills/pr-management-*  (depending on family pick)
-  ...
+  .apache-steward.local.lock
+  .claude/skills/{security,pr-management,setup-isolated-setup,setup-shared-config-sync}-*
+  (and same patterns under .github/skills/ for double-symlinked layouts)
 ```
 
-Then suggest the user `git add` the committed files and open a
-PR.
+Then suggest the user `git add` the committed files and open
+a PR.
 
 ## Failure modes
 
-- **Existing `<repo-root>/.apache-steward/`** → suggest
+- **Existing `<repo-root>/.apache-steward/` and
+  `<committed-lock>` are out of sync** → drift; suggest
   `/setup-steward upgrade`.
-- **Existing committed skill conflicts with a framework skill
-  symlink** → stop, name the conflict, let the user resolve.
-- **Network failure on the snapshot download** → stop, surface
-  the curl/git error. The user retries.
-- **`.gitignore` already mentions `.apache-steward/` but no
-  snapshot is present** → either a partial adopt or a manual
-  cleanup. Re-run is safe; the skill detects this and proceeds.
+- **Existing committed skill conflicts with a framework
+  skill symlink** → stop, name the conflict, let the user
+  resolve.
+- **Network failure on the snapshot download** → stop,
+  surface the curl/git error.
+- **`<committed-lock>` references a method/URL the runtime
+  cannot reach** (e.g. svn-zip URL 404) → surface, ask the
+  user whether the project has retired that release; the
+  user updates `<committed-lock>` deliberately and re-runs.
diff --git a/.claude/skills/setup-steward/upgrade.md b/.claude/skills/setup-steward/upgrade.md
index 6434ed9..35d64d2 100644
--- a/.claude/skills/setup-steward/upgrade.md
+++ b/.claude/skills/setup-steward/upgrade.md
@@ -1,148 +1,220 @@
 <!-- SPDX-License-Identifier: Apache-2.0
      https://www.apache.org/legal/release-policy.html -->
 
-# upgrade — refresh the gitignored snapshot + reconcile overrides
+# upgrade — refresh the gitignored snapshot per the committed lock
 
-Refresh `<repo-root>/.apache-steward/` to a newer framework
-version, surface what changed, and reconcile any agentic
-overrides against the new framework structure.
+The upgrade flow is **drift-driven**. It detects mismatch
+between `<committed-lock>` (project pin) and `<local-lock>`
+(per-machine fetch), then re-installs per the committed lock,
+refreshes symlinks, and reconciles overrides.
+
+Two trigger paths land here:
+
+1. **User-initiated** — explicit `/setup-steward upgrade`,
+   e.g. after a colleague bumped `<committed-lock>` to a
+   new framework version and the user wants to align.
+2. **Drift-triggered from a framework skill** — any
+   framework skill (or `/setup-steward verify`) detected
+   drift on its pre-flight check and the user accepted the
+   proposal to upgrade.
+
+Both paths run the same flow.
 
 ## Inputs
 
-- `from:<git-ref>` — bring the snapshot to a specific framework
-  ref (default: latest `main`).
-- `dry-run` — show what would change without modifying anything.
+- `from:<git-ref>` / `from:<version>` — bring the snapshot
+  to a specific framework ref **for this run only**. Does
+  NOT update `<committed-lock>` (use a project-level commit
+  for that). Useful for testing a candidate version before
+  pinning it.
+- `bump-committed` — also update `<committed-lock>` to the
+  new ref. Use when this run is the project-level decision
+  to move to a newer version (the diff lands in the user's
+  next commit).
+- `dry-run` — show what would change without modifying
+  anything.
 
 ## Step 0 — Pre-flight
 
-1. Read `<repo-root>/.apache-steward.lock` for the current
-   pinned commit SHA. If missing, the repo isn't adopted —
-   suggest `/setup-steward adopt` and stop.
-2. Read `<repo-root>/.apache-steward/` to confirm the snapshot
-   is on disk. If missing (gitignored, fresh clone),
-   re-download to the locked SHA first — that's the
-   recover-snapshot path, not an upgrade. Then continue.
+1. Read `<committed-lock>`. If missing, the repo isn't
+   adopted — suggest `/setup-steward adopt` and stop.
+2. Read `<local-lock>`. If missing (gitignored, fresh
+   clone), the local install hasn't been initialised yet —
+   route as a recover-snapshot install per the committed
+   lock, not as an upgrade. Continue at Step 3.
 
-## Step 1 — Compare locked vs upstream
+## Step 1 — Compute drift
 
-Fetch upstream's latest SHA for the configured ref:
+Compare `<committed-lock>` to `<local-lock>` and to upstream
+where applicable:
 
-```bash
-git ls-remote https://github.com/apache/airflow-steward.git \
-  refs/heads/main
-```
+| Method | Drift signal |
+|---|---|
+| `git-branch` | `<local-lock>.fetched_commit` vs upstream's current tip of `<committed-lock>.ref` (the branch). Drift if upstream has commits the local snapshot does not. |
+| `git-tag` | `<committed-lock>.ref` vs `<local-lock>.source_ref`. Drift if they differ. |
+| `svn-zip` | `<committed-lock>.ref` (version) vs `<local-lock>.source_ref`. Drift if they differ. Also: if `sha512` differs, surface as a security-flagged drift (the released zip changed under the same version — investigate). |
 
-If the locked SHA matches upstream, surface that and stop —
-the snapshot is up to date. The user can re-invoke later.
+Also check method change: if
+`<committed-lock>.method != <local-lock>.source_method`, the
+project switched install methods — surface as a drift that
+needs a full re-install.
 
-Otherwise, list the commits between locked and upstream
-(shallow log via the GitHub API or by re-cloning into a temp
-dir; both work).
+If no drift detected, surface and stop — the local snapshot
+matches the committed pin, no upgrade needed.
 
 ## Step 2 — Surface what changed
 
-Show the user:
+For each kind of drift, present:
 
-- The commit list (`git log --oneline <locked>..<upstream>`).
-- Files touched in the framework `.claude/skills/` directory,
-  grouped by skill family. Call out any change to a skill the
-  adopter has an override for (overrides may need
-  reconciliation — see Step 4).
-- Any change to the `setup-steward` skill itself in the
-  framework — that means the adopter's *committed* copy may
-  have drifted. Surface as an extra note; the adopter chooses
-  whether to re-copy.
+- **Commits between `<local>` and `<committed>`** — for
+  `git-branch` and `git-tag` methods, list the commit log
+  (`git log --oneline <local-commit>..<committed-commit>`)
+  via the GitHub API or by re-cloning to a temp dir.
+- **Files touched in the framework's `.claude/skills/`** —
+  grouped by skill family. Call out any change to a skill
+  the adopter has an override for (the override will need
+  reconciliation in Step 5).
+- **`setup-steward` skill changed in the framework** —
+  surface as a separate note. The adopter's *committed*
+  copy of `setup-steward` may need re-copying from the new
+  snapshot; that is an explicit project-level decision, not
+  auto-applied.
 
-Ask for explicit confirmation before refreshing.
+Ask for explicit confirmation before deleting and re-
+installing.
 
-## Step 3 — Refresh the snapshot
-
-Replace `<repo-root>/.apache-steward/` with a fresh
-`--depth=1` clone at the new ref:
+## Step 3 — Delete the old snapshot
 
 ```bash
 rm -rf .apache-steward
-git clone --depth=1 \
-  --branch <ref> \
-  https://github.com/apache/airflow-steward.git \
-  .apache-steward
 ```
 
-Update `.apache-steward.lock` with the new SHA + date.
+The snapshot is gitignored — destroying it loses no
+committed work. Do this **before** the new install to avoid
+"new layered on top of old" partial state.
 
-If the user is on a UNIX system with hardlink-aware tools, an
-optimization is to clone alongside and `mv` — but a simple
-nuke-and-clone is the canonical path and is what the skill
-defaults to. The snapshot is gitignored anyway, so destroying
-it loses no committed work.
+## Step 4 — Install per the committed lock
 
-## Step 4 — Reconcile overrides
+Per `<committed-lock>.method`:
 
-For each file in `<repo-root>/.apache-steward-overrides/`:
+- **`git-branch`** — `git clone --depth=1 --branch
+  <committed.ref> <committed.url> .apache-steward`. If
+  `from:<git-ref>` was passed, use that branch instead of
+  the committed one (this run only).
+- **`git-tag`** — `git clone --depth=1 --branch
+  <committed.ref> <committed.url> .apache-steward`. If
+  `from:<git-ref>` overrides, use it.
+- **`svn-zip`** — `curl` the zip + verify `sha512` +
+  `unzip` to `.apache-steward/`. The verification step is
+  **mandatory**; mismatched SHA-512 stops the upgrade and
+  surfaces the discrepancy.
+
+After install, capture the actual on-disk state for the
+new `<local-lock>`:
+
+- `source_method`, `source_url`, `source_ref` — from
+  whatever method was used in this run (committed lock or
+  `from:` override).
+- `fetched_commit` — `git -C .apache-steward rev-parse
+  HEAD` for git methods; the version for svn-zip.
+- `fetched_at` — current ISO-8601 timestamp.
 
-1. Check the corresponding framework skill exists in the new
-   snapshot. If not (skill renamed or removed), surface as a
-   conflict — the override may now apply to nothing. The user
-   either updates the override's target skill name or removes
-   the override.
-2. If the framework skill's structure changed in a way the
-   override anchors against (e.g. the override invalidates
-   "Step 5 — Land the valid/invalid consensus" but the
-   framework renumbered or restructured steps), surface as a
-   conflict. The user re-anchors the override against the new
-   structure.
+## Step 5 — Reconcile overrides
 
-The skill **does not** auto-rewrite overrides. It surfaces
-conflicts and lets the user decide; agentic interpretation
-means the right call is human judgement, not pattern-matching.
+For each file in `<repo-root>/.apache-steward-overrides/`:
 
-## Step 5 — Re-create symlinks
+1. **Target skill check** — does the named framework skill
+   exist in the new snapshot? If not (skill renamed or
+   removed):
+   - Surface as conflict.
+   - The user updates the override's target skill name OR
+     deletes the override.
+2. **Anchor check** — if the override references framework
+   structure (step numbers, golden rules, decision-table
+   rows) that has changed in the new framework version:
+   - Surface as conflict, with the specific anchors that
+     have moved.
+   - The user re-anchors the override against the new
+     structure.
+
+The skill **does not** auto-rewrite overrides. Agentic
+interpretation means the right call is human judgement, not
+pattern-matching.
+
+## Step 6 — Refresh framework-skill symlinks
 
 Walk `<adopter-skills-dir>` looking for stale symlinks that
 point at framework skills no longer in the new snapshot
-(rename, removal). For each, ask the user to either:
+(rename, removal). For each:
 
-- Remove the stale symlink (renamed-away skill is gone), or
-- Re-symlink to the new name (if the framework documented a
-  rename).
+- If renamed (the framework documented a rename in its
+  release notes), offer to re-symlink to the new name.
+- If removed, offer to remove the stale symlink.
 
 Then walk the new snapshot for any new framework skills in
 the families the adopter previously picked, and offer to
 symlink them in.
 
-## Step 6 — Sanity check
+For the double-symlinked convention, refresh both layers.
+
+## Step 7 — Update `<local-lock>`
+
+Write the new local lock with the values captured in Step
+4.
+
+## Step 8 — (optional) Update `<committed-lock>`
+
+If `bump-committed` was passed, update
+`<committed-lock>.ref` (and per-method extras: `commit` for
+tag, `sha512` for svn-zip) to the new fetch. Surface the
+diff to the user before writing.
+
+Without `bump-committed`, the committed lock is unchanged —
+this upgrade is a *local* sync to the project pin.
+
+## Step 9 — Sanity check
 
 Run [`verify.md`](verify.md)'s checklist as a final step.
 
 ## Output to the user
 
 ```text
-Snapshot refreshed: <old-SHA> → <new-SHA>
-  X commits pulled (see list above)
-  Y framework skills changed
-  Z framework skills added
-  W framework skills renamed/removed (see Step 5)
-
-Overrides reconciled:
-  ✓ <list of overrides whose target skill is unchanged>
-  ⚠ <list of overrides flagged for re-anchoring>
-
-.apache-steward.lock updated. Symlinks refreshed.
+Drift remediated:
+  Method:    <method>
+  Project:   <committed.ref>      (committed)
+  Local:     <local.fetched-commit-or-version>  → <new>
+  Snapshot:  refreshed at .apache-steward/
+
+Symlinks:
+  ✓ <list of unchanged symlinks>
+  + <list of newly-symlinked framework skills>
+  - <list of removed stale symlinks>
+
+Overrides:
+  ✓ <list of overrides whose target is unchanged>
+  ⚠ <list of overrides flagged for re-anchoring> (open the
+     file and update against the new framework structure)
 
 Recommended follow-ups:
   - Run /setup-isolated-setup-update if the secure-setup blast
     radius (settings.json, agent-isolation/, pinned-versions.toml)
     appears in the diff.
-  - For each ⚠ override, open the file and re-anchor against the
-    new framework structure.
+  - Open .apache-steward-overrides/<name>.md for any ⚠ entry above.
 ```
 
 ## Failure modes
 
-- **`.apache-steward.lock` missing** → repo not adopted yet;
-  suggest `/setup-steward adopt`.
+- **`<committed-lock>` missing** → repo not adopted; suggest
+  `/setup-steward adopt`.
 - **Network failure** → stop, surface error, user retries.
-- **Conflict during reconcile** → not a failure per se; the
-  skill surfaces the conflict and finishes the upgrade up to
-  the conflict. The user's next step is editing the override
-  files.
+  The skill never leaves a half-deleted snapshot — Step 3's
+  `rm -rf` runs only after Step 2's user confirmation.
+- **SHA-512 mismatch on svn-zip** → stop. The released zip
+  has changed content under the same version, which is a
+  serious signal. The user investigates (re-check the
+  project's announcement, re-fetch the official KEYS,
+  consider whether the project re-released).
+- **Conflict during reconcile** → not a failure per se. The
+  skill surfaces the conflict and finishes the upgrade up
+  to Step 7. The user's next step is editing the override
+  file.
diff --git a/.claude/skills/setup-steward/verify.md b/.claude/skills/setup-steward/verify.md
index 3a12ec7..013218f 100644
--- a/.claude/skills/setup-steward/verify.md
+++ b/.claude/skills/setup-steward/verify.md
@@ -1,29 +1,29 @@
 <!-- SPDX-License-Identifier: Apache-2.0
      https://www.apache.org/legal/release-policy.html -->
 
-# verify — read-only health check of the steward integration
+# verify — health check of the steward integration + drift detection
 
 Confirms the framework is wired in correctly so the rest of
-the framework's skills resolve from the right paths. Read-only
-— never modifies anything; surfaces gaps and remediation
-commands.
+the framework's skills resolve from the right paths, and
+surfaces any **drift** between the committed lock (project
+pin) and the local lock (per-machine fetch). Read-only by
+default — surfaces gaps and remediation commands.
 
 ## Inputs
 
 - `--auto-fix-symlinks` — *exception to read-only*. If the
   snapshot is present but symlinks are missing or dangling,
   recreate them. Used by the post-checkout hook
-  ([`adopt.md` Step 6](adopt.md)) on a fresh worktree where
-  the gitignored symlinks didn't follow the checkout.
+  ([`adopt.md` Step 10](adopt.md)) on a fresh worktree
+  where the gitignored symlinks didn't follow the
+  checkout.
 
 ## Pre-flight
 
-1. `git rev-parse --show-toplevel` — must succeed; we need a
-   repo root to resolve relative paths.
-2. Read `git remote get-url origin`. If it resolves to
-   `apache/airflow-steward` (or a fork of), refuse — this
-   skill is for repos that *adopt* the framework, not for
-   the framework itself.
+1. `git rev-parse --show-toplevel` — must succeed.
+2. `git remote get-url origin` — refuse if it resolves to
+   `apache/airflow-steward` itself (this skill is for
+   adopters, not the framework).
 3. If `<repo-root>/.apache-steward.lock` is missing, the
    repo is not adopted. Surface and stop with a pointer at
    `/setup-steward adopt`.
@@ -36,60 +36,91 @@ directory or doc updates — surface every check).
 
 ### 1. Snapshot present + intact
 
-`<repo-root>/.apache-steward/` exists, is a directory, and
-contains the expected top-level files (`README.md`,
-`AGENTS.md`, `.claude/skills/`, `tools/`).
+`<snapshot-dir>/` exists, is a directory, and contains the
+expected top-level files (`README.md`, `AGENTS.md`,
+`.claude/skills/`, `tools/`).
 
-- ✗ if missing → run `/setup-steward upgrade` to re-fetch
-  (it gracefully handles the recover-snapshot case when the
-  lock file exists but the snapshot doesn't).
-- ✗ if missing top-level files → snapshot is corrupted; same
-  remediation.
+- ✗ if missing → run `/setup-steward upgrade` (it
+  gracefully handles the recover-snapshot case when the
+  committed lock exists but the snapshot does not).
+- ✗ if missing top-level files → snapshot is corrupted;
+  same remediation.
+
+### 2. Both lock files exist + parse
+
+`<committed-lock>` (`.apache-steward.lock`) and
+`<local-lock>` (`.apache-steward.local.lock`) both parse.
+
+- ✗ if `<committed-lock>` is missing → not adopted;
+  redirect (already caught in pre-flight).
+- ⚠ if `<local-lock>` is missing or unparsable → first
+  install on this machine has not run, or the file was
+  truncated. Suggest `/setup-steward upgrade` to re-create
+  the snapshot + write the local lock.
+
+### 3. Drift between committed and local locks
 
-### 2. Snapshot pinned to a real commit
+This is the **core drift check**. The same logic every
+framework skill runs at the top of its invocation.
 
-`<repo-root>/.apache-steward.lock` parses, the recorded
-`commit:` SHA matches `git -C .apache-steward rev-parse HEAD`.
+Compare:
 
-- ⚠ if mismatch — somebody modified the snapshot manually.
-  Suggest `/setup-steward upgrade` to re-pin or `git -C
-  .apache-steward checkout <locked-sha>` to revert.
+- `<committed-lock>.method` vs `<local-lock>.source_method`
+- `<committed-lock>.url` vs `<local-lock>.source_url`
+- `<committed-lock>.ref` vs `<local-lock>.source_ref`
+- For `git-branch`: also compare upstream tip (the actual
+  current `HEAD` of the branch on the remote) against
+  `<local-lock>.fetched_commit`.
 
-### 3. `.gitignore` correctly excludes the snapshot + symlinks
+| Result | Severity |
+|---|---|
+| All match (and for `git-branch`, local is at upstream tip) | ✓ |
+| Method or URL differ | ✗ — full re-install needed; remediation: `/setup-steward upgrade` |
+| Ref differs (e.g. project bumped tag, or `git-branch` local is behind upstream) | ⚠ — sync needed; remediation: `/setup-steward upgrade` |
+| `svn-zip` SHA-512 differs from the verification anchor in `<committed-lock>` | ✗ — security-flagged; the released zip changed content; investigate before upgrading |
+
+### 4. `.gitignore` correctly excludes the snapshot + local lock + symlinks
 
 Check that the entries from
-[`adopt.md` Step 3](adopt.md) are present in
-`<repo-root>/.gitignore`. The snapshot path
-`/.apache-steward/` is **required**; the symlink patterns are
-**recommended** (otherwise a fresh clone may try to commit
-dangling symlinks).
+[`adopt.md` Step 7](adopt.md) are present in
+`<repo-root>/.gitignore`. Required:
+
+- `/.apache-steward/` (snapshot path)
+- `/.apache-steward.local.lock` (per-machine state)
+
+Recommended:
+
+- The framework-skill symlink patterns (`security-*`,
+  `pr-management-*`, `setup-isolated-setup-*`,
+  `setup-shared-config-sync`) under both `.claude/skills/`
+  and `.github/skills/` per convention.
 
 - ✗ if `/.apache-steward/` is not gitignored — the snapshot
-  is at risk of being accidentally committed; remediation:
-  add the line.
+  is at risk of being accidentally committed.
+- ✗ if `/.apache-steward.local.lock` is not gitignored —
+  per-machine state would leak into the repo.
 - ⚠ if symlink patterns are not gitignored.
 
-### 4. Symlinks point at live framework skills
+### 5. Symlinks point at live framework skills
 
 For each symlink under `<adopter-skills-dir>` that resolves
 into `.apache-steward/.claude/skills/<name>/`:
 
 - ✓ if the target exists.
 - ✗ if dangling (target deleted or snapshot missing).
-  Remediation: `/setup-steward adopt` (idempotent re-run) or
-  this same skill with `--auto-fix-symlinks`.
+  Remediation: `/setup-steward adopt` (idempotent re-run)
+  or this same skill with `--auto-fix-symlinks`.
 
-For each framework skill in the snapshot that is **not**
-symlinked in the adopter — surface as ⚠ with the family
-classification (`security`, `pr-management`, `setup`). The
-user may have intentionally not picked that family; the
-warning prompts a decision.
+For each framework skill in the snapshot **not** symlinked
+in the adopter — surface as ⚠ with the family
+classification. The user may have intentionally not picked
+that family; the warning prompts a decision.
 
-### 5. `.apache-steward-overrides/` exists + has the README
+### 6. `.apache-steward-overrides/` exists + has the README
 
-`<repo-root>/.apache-steward-overrides/` is a directory with
-the `README.md` scaffold from
-[`adopt.md` Step 5](adopt.md).
+`<repo-root>/.apache-steward-overrides/` is a directory
+with the `README.md` scaffold from
+[`adopt.md` Step 9](adopt.md).
 
 - ✗ if missing → `/setup-steward adopt` (idempotently
   re-creates).
@@ -97,28 +128,30 @@ the `README.md` scaffold from
   may have been hand-created. Suggest re-running
   `/setup-steward adopt`.
 
-### 6. The `setup-steward` skill itself is up to date
+### 7. The `setup-steward` skill itself is up to date
 
 Compare the adopter-side committed `setup-steward` skill
-against the snapshot's `.apache-steward/.claude/skills/setup-steward/`.
+(at `<adopter-skills-dir>/setup-steward/`) against the
+snapshot's `.apache-steward/.claude/skills/setup-steward/`.
 
 - ✓ if same content.
-- ⚠ if different — the adopter's committed copy has drifted.
-  Suggest re-copying from the snapshot. The user may have
-  intentional local tweaks; surface as ⚠ not ✗.
+- ⚠ if different — the adopter's committed copy has
+  drifted. Suggest re-copying from the snapshot per the
+  install recipe. The user may have intentional local
+  tweaks; surface as ⚠ not ✗.
 
-### 7. Post-checkout hook installed
+### 8. Post-checkout hook installed
 
-`<repo-root>/.git/hooks/post-checkout` exists, is executable,
-and contains the `setup-steward verify --auto-fix-symlinks`
-recipe.
+`<repo-root>/.git/hooks/post-checkout` exists, is
+executable, and contains the
+`/setup-steward verify --auto-fix-symlinks` recipe.
 
 - ⚠ if missing — strictly optional, but worktrees off this
-  repo will need a manual `/setup-steward verify
-  --auto-fix-symlinks` after checkout. Print the install
-  recipe.
+  repo will need a manual
+  `/setup-steward verify --auto-fix-symlinks` after
+  checkout. Print the install recipe.
 
-### 8. Project documentation mentions the framework
+### 9. Project documentation mentions the framework
 
 `<repo-root>/README.md` (or another committed doc the
 adopter picked) mentions the steward adoption with a link
@@ -133,11 +166,16 @@ intentionally opted out of), say so explicitly and stop.
 If anything is ✗, end the report with a concrete next-step
 list, ordered most → least urgent:
 
-- ✗ on check 1 → `/setup-steward upgrade` (re-fetches).
-- ✗ on check 4 → `/setup-steward verify
-  --auto-fix-symlinks` (cheap; no-op when symlinks already
-  correct).
-- ✗ on check 5 → `/setup-steward adopt` (idempotent
+- ✗ on check 1 → `/setup-steward upgrade` (re-fetches per
+  the committed lock).
+- ✗ on check 3 (drift) → `/setup-steward upgrade`.
+- ✗ on check 5 (dangling symlinks) →
+  `/setup-steward verify --auto-fix-symlinks` (cheap;
+  no-op when symlinks already correct).
+- ✗ on check 6 → `/setup-steward adopt` (idempotent
   re-create).
+- ✗ on check 4 / SHA-512 mismatch → **investigate first**;
+  do not run upgrade until you understand why the
+  released zip changed under the same version.
 - All other ✗ / ⚠ → name the gap, give the one-line
   remediation.
diff --git a/README.md b/README.md
index bb34b29..1423a45 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,10 @@
 - [Apache Steward (to be renamed)](#apache-steward-to-be-renamed)
   - [How adoption works](#how-adoption-works)
   - [Adopting the framework](#adopting-the-framework)
+    - [1. Bootstrap (copy-pasteable shell)](#1-bootstrap-copy-pasteable-shell)
+    - [2. Skill takeover](#2-skill-takeover)
+    - [Subsequent contributors](#subsequent-contributors)
+    - [Drift detection](#drift-detection)
   - [Skill families](#skill-families)
   - [Maintenance](#maintenance)
   - [Cross-references](#cross-references)
@@ -90,32 +94,87 @@ a gitignored snapshot, and agent-readable override files.
 
 ## Adopting the framework
 
-Tell your agent: **"adopt apache/airflow-steward in my repo"**.
+Two phases — a **shell bootstrap** that gets `setup-steward`
+into your repo, then the **skill takeover** that wires up the
+rest interactively.
 
-The agent should:
+### 1. Bootstrap (copy-pasteable shell)
 
-1. Read this README (you're here).
-2. Copy the
+Pick an install method and follow the verbatim recipe in
+[**`docs/setup/install-recipes.md`**](docs/setup/install-recipes.md):
+
+| Method | When to use | Reproducibility |
+|---|---|---|
+| `svn-zip` | Production once ASF official releases ship to `dist.apache.org` (signed + checksummed) | Frozen by version |
+| `git-tag` | Pin a specific framework version | Frozen by tag |
+| `git-branch` (default `main`) | WIP path — track the framework's `main` directly. The default during the framework's pre-release phase. | Tracks tip |
+
+Each recipe is a single shell block that:
+
+1. Adds `.apache-steward/`, `.apache-steward.local.lock`, and
+   the framework-skill symlinks to `.gitignore`.
+2. Downloads + verifies + extracts the framework into
+   `.apache-steward/` (gitignored — build artefact, not
+   source).
+3. Copies the
    [`setup-steward`](.claude/skills/setup-steward/SKILL.md)
-   skill from this framework into your repo's skill directory,
-   matching your existing convention (flat `.claude/skills/` or
-   the double-symlinked pattern — see
-   [`conventions.md`](.claude/skills/setup-steward/conventions.md)).
-   This is the **only** framework artefact the adopter commits.
-3. Invoke `/setup-steward` to do the rest:
-
-   - download the snapshot into `.apache-steward/` (gitignored),
-   - create symlinks in your skill directory for the families
-     you pick (security and/or pr-management),
-   - scaffold `.apache-steward-overrides/` (committed),
-   - update your repo's `.gitignore`,
-   - install a `post-checkout` git hook so worktrees re-create
-     the gitignored symlinks automatically,
-   - update your project documentation with a brief mention.
+   skill into your skills directory, matching your existing
+   convention (flat `.claude/skills/<n>/` or the
+   double-symlinked `.claude/skills/<n>` →
+   `.github/skills/<n>/` pattern).
+
+After the recipe completes, the framework snapshot is on
+disk and the bootstrap skill is in your repo.
+
+### 2. Skill takeover
+
+Tell your agent: **"adopt apache/airflow-steward in my repo"**
+(or invoke `/setup-steward` directly). The skill walks
+through the rest:
+
+- writes `.apache-steward.lock` (committed) — the project's
+  pin: install method + URL + ref + verification anchor;
+- writes `.apache-steward.local.lock` (gitignored) — what
+  this machine actually fetched + when;
+- asks which skill families (`security`, `pr-management`) to
+  symlink in;
+- creates the gitignored framework-skill symlinks;
+- scaffolds `.apache-steward-overrides/` (committed) for any
+  local workflow modifications;
+- installs a `post-checkout` git hook so worktrees re-create
+  runtime state automatically;
+- updates your project documentation with a brief mention.
 
 After the skill finishes, you commit the small, focused
 diff — the bootstrap skill, the `.gitignore` entries, the
-overrides scaffold, the doc note — and you're done. Open a PR.
+two lock files (committed + gitignore exclusion for the
+local one), the overrides scaffold, the doc note — and you're
+done. Open a PR.
+
+### Subsequent contributors
+
+Future contributors who clone your repo just say "adopt
+apache-steward in this repo" (or invoke `/setup-steward`).
+The skill reads `.apache-steward.lock` (already committed)
+and re-installs to the same version your project pinned. No
+need to redo the manual recipe — the committed lock is the
+project's source-of-truth.
+
+### Drift detection
+
+Every framework skill compares the gitignored
+`.apache-steward.local.lock` against the committed
+`.apache-steward.lock` at the top of its run. If they have
+drifted (project lead bumped the pin, or the local install
+is stale on a `main`-tracking adopter), the skill surfaces
+the gap and proposes `/setup-steward upgrade`. `upgrade`
+deletes the gitignored snapshot, re-installs per the
+committed pin, refreshes the gitignored symlinks, and
+reconciles any agentic overrides — see
+[`docs/setup/install-recipes.md`](docs/setup/install-recipes.md)
+and
+[`.claude/skills/setup-steward/upgrade.md`](.claude/skills/setup-steward/upgrade.md)
+for the full flow.
 
 ## Skill families
 
diff --git a/docs/setup/install-recipes.md b/docs/setup/install-recipes.md
new file mode 100644
index 0000000..4e2c6b7
--- /dev/null
+++ b/docs/setup/install-recipes.md
@@ -0,0 +1,247 @@
+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*
+
+- [Install recipes — bootstrap apache-steward in an adopter repo](#install-recipes--bootstrap-apache-steward-in-an-adopter-repo)
+  - [Method 1 — released zip from ASF distribution](#method-1--released-zip-from-asf-distribution)
+  - [Method 2 — git tag](#method-2--git-tag)
+  - [Method 3 — git branch (defaults to `main`)](#method-3--git-branch-defaults-to-main)
+  - [After any recipe — let the skill take over](#after-any-recipe--let-the-skill-take-over)
+  - [Subsequent runs and drift detection](#subsequent-runs-and-drift-detection)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+<!-- SPDX-License-Identifier: Apache-2.0
+     https://www.apache.org/legal/release-policy.html -->
+
+# Install recipes — bootstrap apache-steward in an adopter repo
+
+Three copy-pasteable shell recipes for fetching the framework
+into a new adopter project's repo. Each recipe is **the
+bootstrap that gets `setup-steward` into the repo**; once it is
+in place, the rest of the adoption (skill-family pick, framework
+symlinks, project doc note, gitignored runtime state) runs
+through `/setup-steward` interactively.
+
+Pick the recipe that matches your distribution preference:
+
+| Method | When to use | Reproducibility |
+|---|---|---|
+| [**svn-zip**](#method-1--released-zip-from-asf-distribution) | Production adopters once the framework ships official ASF releases. Signed + checksummed. | Frozen by version |
+| [**git-tag**](#method-2--git-tag) | Pinning a specific framework version (e.g. for testing a release candidate, or for a cautious adopter who tracks named releases only). | Frozen by tag |
+| [**git-branch**](#method-3--git-branch-defaults-to-main) | WIP path — track the framework's `main` branch directly. The default during the framework's pre-release phase. | Tracks branch tip |
+
+> **Adopter convention** — pick the right `cp` step per your
+> existing skills layout:
+>
+> - **flat** (`.claude/skills/<n>/SKILL.md` directly): copy
+>   `setup-steward` into `.claude/skills/setup-steward/`.
+> - **double-symlinked** (e.g. `apache/airflow` —
+>   `.claude/skills/<n>` → `.github/skills/<n>/`): copy the
+>   content into `.github/skills/setup-steward/` AND symlink
+>   `.claude/skills/setup-steward` → `../../.github/skills/setup-steward`.
+>
+> The `setup-steward` skill itself is the **only** framework
+> artefact you commit. Every other framework skill is wired
+> in by the `setup-steward adopt` flow as gitignored symlinks
+> into the gitignored snapshot.
+
+---
+
+## Method 1 — released zip from ASF distribution
+
+> **Status: forthcoming.** ASF release distribution
+> (`https://dist.apache.org/repos/dist/release/<project>/`)
+> is the canonical home for ASF-blessed releases per the
+> [release-policy](https://www.apache.org/legal/release-policy.html)
+> and [infra release-distribution guidelines](https://infra.apache.org/release-distribution.html).
+> This recipe will be the recommended path once the framework
+> ships its first official release; until then, use
+> [Method 3 — git-branch](#method-3--git-branch-defaults-to-main).
+
+```bash
+# === apache-steward bootstrap — Method 1: signed zip from ASF dist ===
+# Replace <PROJECT> with the host adopter's ASF dist subdirectory
+# (e.g. `airflow` once releases land at
+# https://dist.apache.org/repos/dist/release/airflow/).
+# Replace <VERSION> with the framework version you want.
+
+cd /path/to/your/repo
+
+VERSION=<VERSION>
+PROJECT=<PROJECT>
+DIST_BASE=https://dist.apache.org/repos/dist/release/${PROJECT}
+ZIP=apache-steward-${VERSION}-source-release.zip
+
+# 1. Download zip + signature + checksum, verify, extract to .apache-steward/
+curl -fsSLO ${DIST_BASE}/${ZIP}
+curl -fsSLO ${DIST_BASE}/${ZIP}.sha512
+curl -fsSLO ${DIST_BASE}/${ZIP}.asc
+sha512sum -c ${ZIP}.sha512
+# Optional but recommended — verify the OpenPGP signature against the
+# project KEYS file (see https://infra.apache.org/release-signing.html):
+#   curl -fsSLO ${DIST_BASE}/KEYS
+#   gpg --import KEYS
+#   gpg --verify ${ZIP}.asc ${ZIP}
+
+mkdir -p .apache-steward
+unzip -q ${ZIP} -d .apache-steward
+mv .apache-steward/apache-steward-${VERSION}/* \
+   .apache-steward/apache-steward-${VERSION}/.[!.]* \
+   .apache-steward/ 2>/dev/null
+rmdir .apache-steward/apache-steward-${VERSION}
+rm -f ${ZIP} ${ZIP}.sha512 ${ZIP}.asc
+
+# 2. Copy the `setup-steward` skill into your skills dir.
+#    Pick ONE branch based on your existing convention.
+#
+#    A — flat layout (default):
+cp -r .apache-steward/.claude/skills/setup-steward .claude/skills/setup-steward
+#
+#    B — double-symlinked layout (e.g. apache/airflow):
+# mkdir -p .github/skills .claude/skills
+# cp -r .apache-steward/.claude/skills/setup-steward .github/skills/setup-steward
+# ln -sf ../../.github/skills/setup-steward .claude/skills/setup-steward
+
+# 3. Add gitignore entries (idempotent — re-run is safe)
+cat >> .gitignore <<'GITIGNORE'
+
+# apache-steward — gitignored snapshot of the framework, refreshed
+# by /setup-steward upgrade. Build artefact, not source.
+/.apache-steward/
+
+# Per-machine local-pin file. Records what THIS machine fetched and
+# when. Compared against the committed .apache-steward.lock to
+# detect drift.
+/.apache-steward.local.lock
+
+# Symlinks created by /setup-steward into the gitignored snapshot.
+/.claude/skills/security-*
+/.claude/skills/pr-management-*
+/.claude/skills/setup-isolated-setup-*
+/.claude/skills/setup-shared-config-sync
+# Mirror the same patterns under .github/skills/ if your repo uses
+# the double-symlinked convention.
+/.github/skills/security-*
+/.github/skills/pr-management-*
+/.github/skills/setup-isolated-setup-*
+/.github/skills/setup-shared-config-sync
+GITIGNORE
+
+# 4. Tell your agent: "follow /setup-steward to finish adopting steward."
+#    The skill will write .apache-steward.lock (committed) and
+#    .apache-steward.local.lock (gitignored), ask which skill family
+#    to wire up, create the gitignored framework-skill symlinks, and
+#    update your project docs.
+```
+
+---
+
+## Method 2 — git tag
+
+```bash
+# === apache-steward bootstrap — Method 2: pinned git tag ===
+# Replace <TAG> with the framework tag you want
+# (e.g. `v1.0.0` once tags exist on apache/airflow-steward).
+
+cd /path/to/your/repo
+
+TAG=<TAG>
+git clone --depth=1 \
+    --branch ${TAG} \
+    https://github.com/apache/airflow-steward.git \
+    .apache-steward
+
+# Copy the `setup-steward` skill — pick A or B (see Method 1 step 2)
+cp -r .apache-steward/.claude/skills/setup-steward .claude/skills/setup-steward
+# OR for double-symlinked:
+# cp -r .apache-steward/.claude/skills/setup-steward .github/skills/setup-steward
+# ln -sf ../../.github/skills/setup-steward .claude/skills/setup-steward
+
+# Add gitignore entries (same block as Method 1 step 3 — see there)
+
+# Tell your agent: "follow /setup-steward to finish adopting steward."
+```
+
+---
+
+## Method 3 — git branch (defaults to `main`)
+
+The default WIP path while the framework is pre-release.
+
+```bash
+# === apache-steward bootstrap — Method 3: git branch (default: main) ===
+cd /path/to/your/repo
+
+BRANCH=main   # or another branch you want to track
+git clone --depth=1 \
+    --branch ${BRANCH} \
+    https://github.com/apache/airflow-steward.git \
+    .apache-steward
+
+# Copy the `setup-steward` skill — pick A or B (see Method 1 step 2)
+cp -r .apache-steward/.claude/skills/setup-steward .claude/skills/setup-steward
+# OR for double-symlinked:
+# cp -r .apache-steward/.claude/skills/setup-steward .github/skills/setup-steward
+# ln -sf ../../.github/skills/setup-steward .claude/skills/setup-steward
+
+# Add gitignore entries (same block as Method 1 step 3 — see there)
+
+# Tell your agent: "follow /setup-steward to finish adopting steward."
+```
+
+---
+
+## After any recipe — let the skill take over
+
+Once the recipe completes, `setup-steward` is in your repo and
+the snapshot is on disk (gitignored). Tell your agent:
+
+```text
+follow .claude/skills/setup-steward to adopt steward
+```
+
+(or invoke `/setup-steward` directly). The skill walks through
+the rest:
+
+1. **Pick the skill families** to symlink in (`security`,
+   `pr-management`).
+2. **Write the lock files**:
+   - `.apache-steward.lock` (**committed**) — the project's pin
+     (the method + URL + ref you used in the recipe). Future
+     adopters of *this same repo* re-install per this pin.
+   - `.apache-steward.local.lock` (**gitignored**) — what THIS
+     machine actually fetched (commit SHA, timestamp).
+3. **Create the symlinks** for chosen skill families
+   (gitignored — they target the gitignored snapshot).
+4. **Scaffold `.apache-steward-overrides/`** (committed) for
+   any local workflow modifications.
+5. **Install a `post-checkout` git hook** so worktrees
+   re-create the gitignored runtime state.
+6. **Update your project documentation** with a brief mention
+   of the framework adoption.
+
+After this, adopters fresh-cloning the repo can run
+`/setup-steward` and get the framework provisioned per your
+project's committed `.apache-steward.lock` — no need to redo
+the manual recipe.
+
+## Subsequent runs and drift detection
+
+Every framework skill — and `/setup-steward verify` —
+compares the local lock against the committed lock at the top
+of its run. If they have drifted (e.g. the project lead bumped
+`.apache-steward.lock` to a newer ref, or the local install is
+stale on a `main`-tracking adopter), the skill surfaces the
+gap and proposes:
+
+```text
+/setup-steward upgrade
+```
+
+`upgrade` deletes the gitignored snapshot, re-installs per the
+committed lock, refreshes the gitignored symlinks (adding any
+new framework skills, removing any that were renamed away),
+and updates the local lock. See
+[`setup-steward/upgrade.md`](../../.claude/skills/setup-steward/upgrade.md)
+for the full flow.