Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions .github/prompts/release-content.prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ refresh, first run `.github/prompts/update-book.prompt.md`.
**What changed:** <content-change summary, or inspect the pending content diff>
**Baseline release:** <last published content-v tag; resolve it if unset>
**Version bump:** <major | minor | patch | explicit x.y or x.y.z>
**Publication boundary:** open a PR and stop; a human reviews and merges it.
**Publication boundary:** open a PR and stop; owner approval authorizes the separate release controller.

## Sources of truth

Expand Down Expand Up @@ -85,14 +85,16 @@ refresh, first run `.github/prompts/update-book.prompt.md`.
8. **Open a PR using the available PR-creation tool, or update the existing preparation PR
for this branch.** Do not create duplicate handoffs. Describe the baseline/target,
reader-visible changes, verification, and editorial verdict. Wait for the validation
workflow and leave the PR for human review. Do not merge, create a release/tag, or
dispatch publishing as part of preparation.
workflow and leave the PR for human review. Keep infrastructure changes out of a content
PR. Do not merge, create a release/tag, or dispatch publishing as part of preparation.

## After the human merges
## After owner approval

`validate-book.yml` builds and gates the exact artifacts consumed by both publishing
workflows. `deploy-pages.yml` publishes the online site without the gated PDF;
`release-content.yml` creates `content-v<edition>` with the matching
The owner reviews a non-owner-authored PR, or comments `/release-book <full-40-character-head-sha>`
on their own PR (GitHub does not allow self-approval). The controller rechecks the exact
head and content-only scope, validates and merges it, then explicitly validates the merge
and publishes the online site without the gated PDF. `release-content.yml` creates
`content-v<edition>` with the matching
`gh-aw-book-v<edition>.pdf` and changelog notes. Verify both workflow outcomes, the release
asset, and the live version-history page before calling the edition published.

Expand Down
4 changes: 3 additions & 1 deletion .github/prompts/update-book.prompt.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ completed preparation handoff, not a claim of publication. On a later fresh upda
ignore prepared historical plans when deciding whether to resolve a new latest target.
The reusable
validation workflow checks source/evidence, pinned compilation, and site/PDF builds before
the existing publishing workflows can run. Leave the PR for human review and merge.
publication. Leave the content-only PR for owner approval; the release controller handles
an eligible approved merge. Keep workflow, script, dependency and presentation changes
in a separate PR.

## Coordination and stopping rules

Expand Down
3 changes: 2 additions & 1 deletion .github/skills/playbook-orchestration/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ For each chapter in the wave, run **research in parallel**, then author, verify,
Covered changes invalidate acceptance and require renewed review.
9. `frontend-builder` regenerates the existing site/PDF. Run the `release-content` prompt
to choose an appropriate prose edition, update notes, and prepare a PR. The reusable
validation workflow gates publishing; human review and merge remain the release boundary.
validation workflow gates publishing; explicit owner approval triggers the separate
content-only release controller.
Mark the plan prepared with its edition, source fingerprint and PR URL, without claiming
publication. Retries reuse the appropriate proposed edition rather than bumping twice.

Expand Down
13 changes: 13 additions & 0 deletions .github/workflows/book-review-signal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: Book review signal

on:
pull_request_review:
types: [submitted, dismissed]

permissions: {}

jobs:
signal:
runs-on: ubuntu-latest
steps:
- run: echo "Review event received; trusted controller independently checks authorization."
28 changes: 26 additions & 2 deletions .github/workflows/deploy-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ on:
branches: [main]
paths:
- "site/**"
- "content/**"
- "examples/**"
- "assets/**"
- "scripts/**"
- "analytics/**"
Expand All @@ -28,7 +26,23 @@ concurrency:
cancel-in-progress: true

jobs:
published-source:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- name: Require content to match the last published edition
env:
GH_TOKEN: ${{ github.token }}
run: |
TAG="$(python -c "import sys; sys.path.insert(0,'scripts'); import release_content as r; print(r.select_base(r.ROOT, env={})[0])")"
gh release view "$TAG" --repo "$GITHUB_REPOSITORY" --json isDraft --jq '.isDraft == false' | grep -qx true
git diff --quiet "$TAG" HEAD -- content examples
validate:
needs: published-source
uses: ./.github/workflows/validate-book.yml
with:
artifact-name: validated-pages
Expand All @@ -38,7 +52,13 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
pages: write
steps:
- name: Checkout trusted Pages helper
uses: actions/checkout@v4
with:
ref: main
persist-credentials: false
- name: Download the validated site and analytics beacon
uses: actions/download-artifact@v4
with:
Expand All @@ -50,14 +70,18 @@ jobs:

- name: Publish the validated site to gh-pages
run: |
test "$(GH_TOKEN="$GITHUB_TOKEN" gh api "repos/$GITHUB_REPOSITORY/commits/main" --jq .sha)" = "$SOURCE_SHA"
GH_TOKEN="$GITHUB_TOKEN" python scripts/publish_pages.py check
cd artifact/site
touch .nojekyll
git init -q -b gh-pages
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A
git commit -q -m "Deploy ${SOURCE_SHA} from ${GITHUB_REF_NAME}"
DEPLOY_SHA="$(git rev-parse HEAD)"
git push -q --force "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" gh-pages
GH_TOKEN="$GITHUB_TOKEN" python "$GITHUB_WORKSPACE/scripts/publish_pages.py" build --commit "$DEPLOY_SHA"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SOURCE_SHA: ${{ needs.validate.outputs.source-sha }}
124 changes: 124 additions & 0 deletions .github/workflows/release-approved-book.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
name: Release approved book

on:
issue_comment:
types: [created, edited, deleted]
workflow_run:
workflows: ["Book review signal", "Check book update"]
types: [completed]
workflow_dispatch:
inputs:
pull-request:
description: "Content PR number for an explicit retry"
required: true
type: number
schedule:
- cron: "17 */6 * * *"

permissions:
contents: read
pull-requests: read
checks: read

concurrency:
group: release-approved-book
cancel-in-progress: false

jobs:
select:
if: >-
github.ref == 'refs/heads/main' &&
(github.event_name != 'issue_comment' ||
(github.event.issue.pull_request && github.event.comment.user.login == 'webmaxru' &&
github.event.action != 'deleted' && startsWith(github.event.comment.body, '/release-book ')))
runs-on: ubuntu-latest
outputs:
number: ${{ steps.select.outputs.number }}
head: ${{ steps.select.outputs.head }}
base: ${{ steps.select.outputs.base }}
comparison: ${{ steps.select.outputs.comparison }}
merge-sha: ${{ steps.select.outputs.merge_sha }}
steps:
- uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
persist-credentials: false
- id: select
env:
GH_TOKEN: ${{ github.token }}
EVENT_NAME: ${{ github.event_name }}
COMMENT_PR: ${{ github.event.issue.pull_request.url && github.event.issue.number || '' }}
INPUT_PR: ${{ inputs.pull-request }}
run: |
args=()
if [[ "$EVENT_NAME" == "issue_comment" ]]; then
if [[ -z "$COMMENT_PR" ]]; then exit 0; fi
args=(--number "$COMMENT_PR")
elif [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
args=(--number "$INPUT_PR")
fi
python scripts/approved_book.py select "${args[@]}" > selected.json
python - <<'PY'
import json, os, subprocess
data = json.load(open("selected.json"))
if data:
comparison = data["comparison"]
subprocess.run(["git", "merge-base", "--is-ancestor", comparison, data["base"]], check=True)
with open(os.environ["GITHUB_OUTPUT"], "a") as out:
for key in ("number", "head", "base"):
print(f"{key}={data[key]}", file=out)
print(f"comparison={comparison}", file=out)
print(f"merge_sha={data.get('merge_sha', '')}", file=out)
PY

preflight:
needs: select
if: needs.select.outputs.number != '' && needs.select.outputs.merge-sha == ''
uses: ./.github/workflows/validate-book.yml
with:
source-ref: ${{ needs.select.outputs.head }}
comparison-base: ${{ needs.select.outputs.comparison }}
artifact-name: approved-head
require-content-change: true

merge:
needs: [select, preflight]
if: needs.select.outputs.number != '' && needs.select.outputs.merge-sha == '' && needs.preflight.outputs.source-sha == needs.select.outputs.head
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
checks: read
outputs:
merge-sha: ${{ steps.merge.outputs.merge_sha }}
steps:
- uses: actions/checkout@v4
with:
ref: main
persist-credentials: false
- id: merge
env:
GH_TOKEN: ${{ github.token }}
NUMBER: ${{ needs.select.outputs.number }}
HEAD: ${{ needs.select.outputs.head }}
BASE: ${{ needs.select.outputs.base }}
COMPARISON: ${{ needs.select.outputs.comparison }}
run: |
python scripts/approved_book.py merge --number "$NUMBER" --head "$HEAD" --base "$BASE" --comparison "$COMPARISON" > merged.json
python -c 'import json,os; print("merge_sha="+json.load(open("merged.json"))["merge_sha"], file=open(os.environ["GITHUB_OUTPUT"],"a"))'

publish:
needs: [select, merge]
if: always() && needs.select.result == 'success' && needs.select.outputs.number != '' && (needs.merge.result == 'success' || needs.select.outputs.merge-sha != '')
uses: ./.github/workflows/release-content.yml
permissions:
contents: write
pull-requests: read
checks: read
pages: write
with:
pull-request: ${{ needs.select.outputs.number }}
source-sha: ${{ needs.merge.outputs.merge-sha || needs.select.outputs.merge-sha }}
merge-base: ${{ needs.select.outputs.base }}
comparison-base: ${{ needs.select.outputs.comparison }}
Loading
Loading