Skip to content

Retire references to the 30-day self-hosted trial in two blog posts - #21576

Open
workprentice[bot] wants to merge 1 commit into
masterfrom
fix_issue_21239_retire_30_day_self_hosted_trial_claims
Open

workprentice[bot] wants to merge 1 commit into
masterfrom
fix_issue_21239_retire_30_day_self_hosted_trial_claims

Conversation

@workprentice

@workprentice workprentice Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Closes #21239 (Ask 2 only — see note on scope below).

What changed

PR #19434 switched self-hosted trial language sitewide from "sign up for the 30-day trial" to "request a Proof of Concept (PoC)", and /pricing/ and /product/self-hosted/ reflect that today. Two blog posts were missed by that sweep and still advertised the retired 30-day trial:

  • content/blog/business-critical-launch/index.md (2022) — the meta_desc and four body mentions referenced "a 30 day Self-Hosted Pulumi Service trial."
  • content/blog/self-hosted-search-and-deploy/index.md (2024) — "you can try it out with our 30 day trial" and "to start a 30-day trial."

Both posts now use the current "request a Proof of Concept (PoC)" phrasing and link to the same /product/self-hosted/#self-hosted-trial anchor #19434 established as canonical. I re-ran a sitewide sweep for 30[- ]?day.{0,60}trial after the edit; the only remaining hits are unrelated third-party trials mentioned in other blog posts (Aiven, Confluent Cloud), which are out of scope.

Editorial judgment

The 2022 launch post's body prose narrates the original announcement ("we are excited to launch today...", "this is why we have launched a 30 day trial, instead of 14 day"). Since issue #21239's own proposed default was to bring both posts fully in line with the current PoC language (matching #19434's approach), I updated the announcement prose itself rather than leaving the retired trial length as a historical detail — a reader today should not be told to "sign up for the 30 day trial" for an offer that no longer exists.

Scope note

Issue #21239 also raised a separate Ask 1 about which edition(s) get 12x5 vs. 24x7 support (a pricing-table question for @jeffmerrick, tied to the in-flight #21119). That question is not addressed here — this PR only applies the Ask 2 default, which the issue said would apply automatically if there was no response by 2026-09-11. No response arrived, so I'm opening this now with data/pulumi_pricing.yaml and the comparison table left untouched.

Validation

  • node ./scripts/lint/lint-markdown.js — 0 errors on both files
  • ./scripts/prettier.sh --check — clean
  • Front-matter YAML on both files parses correctly
  • Confirmed no other open PR touches either file

🧠 This PR was created by workprentice.

Two blog posts still advertised a self-hosted trial length that PR #19434 already retired sitewide. Update both to the current Proof of Concept (PoC) language and anchor used by /pricing/ and /product/self-hosted/.

Fixes #21239 (Ask 2 only; Ask 1 is a separate, unresolved pricing-table question for jeffmerrick).
@github-actions github-actions Bot added the review:triaging Claude Triage is currently classifying the PR label Sep 12, 2026
@github-actions github-actions Bot added domain:blog PR touches blog posts or customer stories review:in-progress Claude review is currently running and removed review:triaging Claude Triage is currently classifying the PR labels Sep 12, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Optional style suggestions from the pre-merge review — apply or dismiss; none of them block.


Generated by Claude Code

## Self-Hosted Pulumi Service

We are also announcing today a 30 day trial for self-hosting the [Pulumi Service](/product/pulumi-service/). Self-Hosted Pulumi Service allows you to deploy and manage the Pulumi Service on your own infrastructure, either in the cloud or on-premises. Self-hosting is an option for organizations that need to maintain complete control over hosting, network isolation, identity, and data ownership in order to satisfy segment-specific compliance requirements. It can take time to install and configure a self-hosted product in your desired deployment option, and we want to make sure there is enough time to assess the product. This is why we have launched a 30 day trial, instead of 14 day like the Pulumi Service. Sign up today for the 30 day trial on our [Self-Hosted Pulumi Service page](/product/self-hosted/).
We are also announcing today a Proof of Concept (PoC) program for self-hosting the [Pulumi Service](/product/pulumi-service/). Self-Hosted Pulumi Service allows you to deploy and manage the Pulumi Service on your own infrastructure, either in the cloud or on-premises. Self-hosting is an option for organizations that need to maintain complete control over hosting, network isolation, identity, and data ownership in order to satisfy segment-specific compliance requirements. It can take time to install and configure a self-hosted product in your desired deployment option, and we want to make sure there is enough time to assess the product. This is why we offer a guided PoC rather than a fixed trial window. [Request a PoC](/product/self-hosted/#self-hosted-trial) on our Self-Hosted Pulumi Service page.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[style] wordiness — Single space; drops filler 'in order to'.

Suggested change
We are also announcing today a Proof of Concept (PoC) program for self-hosting the [Pulumi Service](/product/pulumi-service/). Self-Hosted Pulumi Service allows you to deploy and manage the Pulumi Service on your own infrastructure, either in the cloud or on-premises. Self-hosting is an option for organizations that need to maintain complete control over hosting, network isolation, identity, and data ownership in order to satisfy segment-specific compliance requirements. It can take time to install and configure a self-hosted product in your desired deployment option, and we want to make sure there is enough time to assess the product. This is why we offer a guided PoC rather than a fixed trial window. [Request a PoC](/product/self-hosted/#self-hosted-trial) on our Self-Hosted Pulumi Service page.
We are also announcing today a Proof of Concept (PoC) program for self-hosting the [Pulumi Service](/product/pulumi-service/). Self-Hosted Pulumi Service allows you to deploy and manage the Pulumi Service on your own infrastructure, either in the cloud or on-premises. Self-hosting is an option for organizations that need to maintain complete control over hosting, network isolation, identity, and data ownership to satisfy segment-specific compliance requirements. It can take time to install and configure a self-hosted product in your desired deployment option, and we want to make sure there is enough time to assess the product. This is why we offer a guided PoC rather than a fixed trial window. [Request a PoC](/product/self-hosted/#self-hosted-trial) on our Self-Hosted Pulumi Service page.

![Self-Hosted Screenshot](self-hosted.png)

The Self-Hosted Pulumi Service can be installed in any on-premises or cloud provider environment. It can also run in air-gapped environments without access to the Internet, including those with the most demanding compliance needs such as FedRAMP. In concert with the launch of the 30 day trial, we have made the [deployment reference architectures](https://github.com/pulumi/pulumi-self-hosted-installers) public. For assistance setting up your environment or if you don’t see your desired deployment option, [contact us](/contact/). Full details on self-hosting the Pulumi Service can be found in our [Self-Hosted Pulumi Service documentation](/docs/administration/self-hosting/).
The Self-Hosted Pulumi Service can be installed in any on-premises or cloud provider environment. It can also run in air-gapped environments without access to the Internet, including those with the most demanding compliance needs such as FedRAMP. In concert with the launch of the Self-Hosted PoC program, we have made the [deployment reference architectures](https://github.com/pulumi/pulumi-self-hosted-installers) public. For assistance setting up your environment or if you don’t see your desired deployment option, [contact us](/contact/). Full details on self-hosting the Pulumi Service can be found in our [Self-Hosted Pulumi Service documentation](/docs/administration/self-hosting/).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[style] wordiness — Plainer word, same meaning.

Suggested change
The Self-Hosted Pulumi Service can be installed in any on-premises or cloud provider environment. It can also run in air-gapped environments without access to the Internet, including those with the most demanding compliance needs such as FedRAMP. In concert with the launch of the Self-Hosted PoC program, we have made the [deployment reference architectures](https://github.com/pulumi/pulumi-self-hosted-installers) public. For assistance setting up your environment or if you don’t see your desired deployment option, [contact us](/contact/). Full details on self-hosting the Pulumi Service can be found in our [Self-Hosted Pulumi Service documentation](/docs/administration/self-hosting/).
The Self-Hosted Pulumi Service can be installed in any on-premises or cloud provider environment. It can also run in air-gapped environments without access to the Internet, including those with the most demanding compliance needs such as FedRAMP. In concert with the launch of the Self-Hosted PoC program, we have made the [deployment reference architectures](https://github.com/pulumi/pulumi-self-hosted-installers) public. For help setting up your environment or if you don’t see your desired deployment option, [contact us](/contact/). Full details on self-hosting the Pulumi Service can be found in our [Self-Hosted Pulumi Service documentation](/docs/administration/self-hosting/).

@github-actions

Copy link
Copy Markdown
Contributor

Reviewer's guide v1 — not for the author

Tip

This is the reviewer's guide. Work through the ⚠️ checklist below, then approve — approving asserts only that the ⚠️ items looked right to you. Machine-verified this run: links, shortcodes, page metadata, and every claim marked verified (receipts on the evidence page). Code samples are read, not compiled.

PR author: your to-do list is the other review comment, "Author action guide" — nothing on this card is yours.

Note

What this PR changes:

  • business-critical-launch (2022): the announced "30 day Self-Hosted trial" becomes a "Self-Hosted Proof of Concept (PoC) program" in the body, headings prose, and meta_desc.
  • business-critical-launch: the sign-up sentence becomes "Request a PoC" linking /product/self-hosted/#self-hosted-trial.
  • self-hosted-search-and-deploy (2024): two "30 day trial" mentions become "request a Proof of Concept (PoC)", both linking the same anchor.

The wrongness that would block a reader is a dead offer — a link or a call to action pointing at a trial that no longer exists. Fact-checking, link and anchor resolution, frontmatter, and a prose readthrough of both posts all ran.

Review confidence:

Dimension Level Notes
mechanics HIGH
facts HIGH
coherence MEDIUM Both posts are dated announcements, so the rewrite changes what they say was announced on the day — a judgment call — see the first ⚠️ item below.

⚠️ Check these before approving

ID Where Finding
F1 content/blog/business-critical-launch/index.md L35-75 The 2022 post now reads "we are excited to launch today … a Self-Hosted Pulumi Service Proof of Concept (PoC) program", which is not what was announced that day — confirm you want the dated announcement restated, rather than left intact with a note that the trial has since been replaced.
F2 content/blog/self-hosted-search-and-deploy/index.md L1-20 Neither post gets an updated: 2026-09-12 stamp, so the revision is invisible to readers — decide whether these edits are worth surfacing (the repo convention is updated:, never lastmod).

I checked the parts a reviewer would otherwise have to look up: /product/self-hosted/#self-hosted-trial resolves — the self-hosted-trial section id is in layouts/product/self-hosted.html L120 — and all 6 factual claims, including the 350% year-over-year figure, came back clean.

Editorial stances introduced by this PR

Superlative, ranking, or comparative language the diff adds. No verdict — a page's own framing isn't fact-checkable — but confirm each is a stance the docs should take, and that no agent-written rewrite introduced it unasked.

None — the extractor found no positioning or comparison language in this PR's added lines.

✅ What you can rubber-stamp

  • Facts: 6 factual claims checked — 6 verified clean.
  • Mechanics: frontmatter sweep ran; 2 added internal link(s) resolve.
  • Style: 5 advisory suggestion(s) left with the author; never blocking.

💡 Pre-existing issues in touched files: 0 — details on the evidence page.

📎 Full evidence: verification trail, investigation log, review history.

Review v1 · updated 2026-09-12T14:16:36Z · head commit 7f58847


For the reviewer: the ⚠️ items above are the minutes that matter — the receipts for everything machine-verified are on the evidence page. The author's open items live on their own card (the comment headed "Author action guide"); while any are open, a Waiting on the author list above tracks them, and merge stays blocked until they're answered. If something here seems off, comment on the PR — @claude <your point> #update-review re-adjudicates with your input.

@github-actions

Copy link
Copy Markdown
Contributor

Author action guide v1 — nothing blocks merge

Note

Nothing here blocks merge — no open items need an answer from you. A human reviewer still approves the merge.

This PR replaces the retired 30-day self-hosted trial with the Proof of Concept (PoC) program in two older blog posts; the review checked the facts, the new links and their anchor, and the surrounding prose.

🚨 Fix or disagree

Nothing to fix — this section is empty.

❓ Questions for you

No open questions for you.

Style suggestions

Optional polish from pattern-based linting — never blocking, not counted above. Take the ones that read better and ignore the rest. ✏️ marks one you can apply from the Files changed tab — use Add suggestion to batch on each, then Commit suggestions to take several in a single commit.

content/blog/business-critical-launch/index.md
  • line 35: [style] wordiness — 'purchase' is too wordy.
  • line 65: [style] spacing — 's. S' should have one space. ✏️
  • line 65: [style] wordiness — 'in order to' is too wordy. ✏️
  • line 65: [style] wordiness — 'satisfy' is too wordy. ✏️
  • line 69: [style] wordiness — 'assistance' is too wordy. ✏️

📎 Full evidence: verification trail, investigation log, review history.

Review v1 · updated 2026-09-12T14:16:36Z · head commit 7f58847


How to answer

Every 🚨 and ❓ item above needs one of these before merge:

  1. Fix it — push the change. If your push lines up with the flagged lines, this card shows a 🔄 banner within a minute and then refreshes itself. No banner? Comment:

    @claude I pushed a fix for F1 #update-review
    
  2. Disagree with it — say which item and why; the review re-checks with your input:

    @claude F2: <your reasoning — e.g. where the figure comes from> #update-review
    

    Either way your answer counts: the review marks the item resolved, or keeps it with a 🛡️ note for your human reviewer to weigh — it stops blocking merge in both cases.

  3. Accept it as-is — you own the PR; a one-line reason is a valid answer, and your reviewer sees it beside the finding:

    @claude F2: accepting as-is — <your reason> #update-review
    

    To accept every open item at once: @claude accepting all open items — <reason> #update-review.

The #update-review hashtag matters — it routes your reply to this review, and (besides pushing a fix) it is the only reply that unblocks merge. A bare @claude gets you ad-hoc help and leaves this card — and the merge block — untouched.

Please don't edit, hide, or delete this comment — it is the review's record. Full mechanics: CONTRIBUTING.md §AI-assisted contributions.

@github-actions github-actions Bot added review:no-blockers Claude review completed cleanly; outstanding is empty and removed review:in-progress Claude review is currently running labels Sep 12, 2026
@pulumi-bot

Copy link
Copy Markdown
Collaborator

@CamSoper
CamSoper requested a review from cnunciato September 14, 2026 22:58
@workprentice
workprentice Bot enabled auto-merge (squash) September 15, 2026 00:11
CamSoper pushed a commit that referenced this pull request Sep 16, 2026
Three things a first-time reader could not work out from the page.

The board now carries its own manual, folded at the top, and the same
content ships as pr-review:references:reading-the-board: what the page is,
the four verdicts, how Do next relates to the rows, what a row's chips and
buttons mean, what a judgment badge claims, how the literal filters work,
and what the command at the bottom does.

Do-next cards were a batch command with no stated relationship to the rows
below them. Each card now states a fact naming its PRs, says in one
sentence what pressing it does ("Approves and squash-merges #21576, then
merges master into #21432 so it can follow"), and presses those rows' own
buttons rather than adding a parallel fragment. Pick a contrary decision on
one of a card's PRs and the card goes out, so the composed command cannot
contradict itself. A chain or a consolidation has no row button of its
own, so it claims its PRs instead and the same rule applies. "2 generated
rows" is gone: cards name PRs and say a workflow opened them.

And a chip's tooltip described nothing -- it repeated the chip. Every
reason code now has a sentence explaining what it means for that row, with
the raw code kept in parentheses so the queue stays greppable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf
CamSoper pushed a commit that referenced this pull request Sep 16, 2026
Three things a first-time reader could not work out from the page.

The board now carries its own manual, folded at the top, and the same
content ships as pr-review:references:reading-the-board: what the page is,
the four verdicts, how Do next relates to the rows, what a row's chips and
buttons mean, what a judgment badge claims, how the literal filters work,
and what the command at the bottom does.

Do-next cards were a batch command with no stated relationship to the rows
below them. Each card now states a fact naming its PRs, says in one
sentence what pressing it does ("Approves and squash-merges #21576, then
merges master into #21432 so it can follow"), and presses those rows' own
buttons rather than adding a parallel fragment. Pick a contrary decision on
one of a card's PRs and the card goes out, so the composed command cannot
contradict itself. A chain or a consolidation has no row button of its
own, so it claims its PRs instead and the same rule applies. "2 generated
rows" is gone: cards name PRs and say a workflow opened them.

And a chip's tooltip described nothing -- it repeated the chip. Every
reason code now has a sentence explaining what it means for that row, with
the raw code kept in parentheses so the queue stays greppable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf
CamSoper added a commit that referenced this pull request Sep 16, 2026
* Refactor /pr-review into a batch adjudication queue

`/pr-review` with no argument now renders a queue of every open PR the
approver can act on, sorted into stamp / judge / route / blocked with
collision clusters pinned first; `/pr-review N` renders one PR as that row
expanded. One data model (queue.json), one act layer, two renderings.

New deterministic scripts in scripts/review-v3/ (covered by
make test-review-pipeline):

- gh_client.py: GitHub adapter with gh, REST-token and snapshot backends;
  owns the `author:app/<slug>` search rewrite for GitHub App authors.
- pr_review_config.py: ~/.pr-review.yml (me: lanes, stamp_max_lines,
  stale_date_days).
- collect.py: facts per open PR incl. the parsed pinned review
  (review-worklist.py, both surfaces), REVIEW_STATE, triage prose,
  reviewed-head SHA, preview links, and the trust axes / risk tier /
  AI-suspect ported from the retired shell scripts. Cached per (PR, head).
- analyze.py: one verdict per PR with reason codes and row actions;
  cross-PR collision clusters (overlap vs same-file), directional link
  conflicts against the Hugo aliases map, duplicates, stale blog dates,
  self-accepted findings, stale reviews, stale brief summaries.
- render.py: the board (Artifact), the detail view, and --terminal.
- act.py: plan -> preview -> execute for --stamp (per-PR preflight
  immediately before each squash-merge; humans approve-only unless
  --merge-humans), --route, --unblock, --fix, --close, --refresh,
  --render, --deploy, with the attribution footer on every posted
  comment except the approval body.
- screenshot.mjs: Playwright helper for --render.

The 10-step SKILL.md is replaced; action-preview-templates.md and
execution-results.md fold into the act preview/report; action-menus.md
becomes the row action bar plus the bot-branch push policy (dependabot and
generated-docs regens never; workprentice and content-review/* as merge
commits); infrastructure-deployment.md becomes the --deploy row action.
The four pr-review shell scripts are deleted. The dashboard's PR section
is retired in favour of a pointer to /pr-review.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* Keep diff quotes from widening the board

A judgment box's diff quote is white-space: pre with its own horizontal
scroll, but the row's grid cell had no min-width, so a long quoted line
widened the cell and the whole page scrolled sideways. min-width: 0 on the
cell keeps the scroll inside the quote box; overflow-wrap: anywhere lets
the long deep links and summaries wrap.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* Show a fixed disposition as already done, not a recommendation

"recommend fixed" read as a to-do beside an "apply fixes" button that
only appears for drafted descriptions and one-click suggestions. A fixed
disposition means the diff already addresses the finding, so the judgment
line now says "already fixed in the diff"; the other four dispositions
keep "recommend". SKILL.md says so, and tells the publish step to render
with --artifact.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* Hide PRs handed off to another reviewer behind a waiting list

A PR whose requested reviewers include a human who isn't the approver
(and don't include the approver) is waiting on them, not on the queue.
analyze.py marks it handed_off (teams map through review-routing.yml to
the approver's lanes; bots never count), collect.py learns who the
approver is from GET /user, and render.py folds those rows into one
compact "Waiting on others" list -- who, how old, red CI or conflict
flags -- with --include-handed-off to bring the full rows back.

The review request is the memory: it lives on the PR, every session sees
it, and GitHub clears it when the reviewer acts, which is when the row
should return. --route is therefore also "don't show me this again".

Collisions and directional conflicts against a handed-off PR are advisory
(`:theirs`): they no longer gate a stamp, and a cluster with at most one
of the approver's PRs drops out of the pinned slots.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* Drop the row checkboxes; action buttons are the only selectors

The checkbox and the "approve & merge" button on a stamp row composed the
same fragment, and a judge row's checkbox and its "approve as-is" button
could put `--stamp N --force` on the command line twice. One control per
action now: every button is a toggle (aria-pressed), a stamp row's stamp
button starts selected so the default command still merges the whole
stamp set, and the composer dedupes fragments by value as a backstop.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* Add --request-changes: send a judge row back to its author

The old menus always offered "Request changes"; the act layer had no
equivalent, so a judge row whose findings were the author's to fix (a
page with no nav placement, a claim needing a source) offered only
approve-as-is or route. `act.py --request-changes N` posts a
CHANGES_REQUESTED review built from the row's judgments, one
line-anchored item each in the voice message-templates.md sets for the
author type, and applies needs-author-response. Every judge row carries
the button; the judge step may recommend it.

A judgment now separates `decision` (the question the approver answered),
`ask` (the sentence the author reads if the row goes back) and `note`
(the approver's rationale, never sent).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* pr-review: ADHD-friendly board redesign

Make the next move obvious from the top of the page instead of asking the
approver to reconstruct it from chips and a clusters section.

- "Do next" strip at the top: one card per consolidation, chain,
  request-changes batch, route target, and the stamp set, each with the
  composed command.
- One cluster chip per row (cluster:C1:overlap:i/n | same-file | theirs)
  in place of a chip per colliding PR; directional codes capped at one
  plus a "+N more" chip.
- Reason chips split into a primary tier and a folded "why" tier.
- Judgment boxes fold their diff quotes and show a disposition badge.
- One right-aligned, colour-coded primary button per row (the
  recommended action), with the rest as secondary toggles.
- "N of M decisions made" progress line and a judge/route view toggle.
- Clusters section folded at the bottom with a per-cluster
  recommendation (theirs / ignore / consolidate / chain); consolidate
  counts only members whose hunks overlap.
- act.py --chain C1: stamp the first link, unblock the next.
- Summary picker skips blockquote and GitHub-alert lines.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* pr-review board: one decision per row

Every action button was an independent toggle, so a row could compose
"send back to author", "apply fixes" and "approve as-is" at once. Buttons
now carry data-kind: a decision (stamp, request-changes, route, unblock,
refresh, close) is exclusive per row and lighting a second puts out the
first; side actions (fix, render, deploy) ride alongside. The progress
line counts decisions only.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* pr-review: link-only sweeps are mine, route to teams, literal filters

- analyze.link_only_diff: a diff where every changed line is the same
  sentence with only a link rewritten (markdown link text/target, href,
  bare URL or site path) gets shape:link-only. With the new
  ~/.pr-review.yml key `link_fixes: mine` (the default) such a row
  bypasses the lane check (link-fixes:mine) instead of routing to the
  lane owner; `route` keeps the old behaviour.
- collect.routing_teams asks GitHub whether each team in
  review-routing.yml exists; analyze targets the team when it does and
  the SLA person otherwise, marking the row route:no-team.
- Board filter chips are literal: a row shows only while its value is
  lit in every group; a group with nothing lit empties the board and
  says so, with a reset. The dead "no chip = no filter" rule is gone.
- The route verdict chip no longer escapes its model-recommendation
  span into visible markup.
- analyze_pr clears a stale `recommended`; merge_judgments rebuilds the
  Do-next strip so the send-back card reflects the merged file.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* pr-review: --rerun N for errored or absent reviews

A review:error row named its blocker and waited. It now carries the
pipeline's own escape hatch: --rerun N comments `@claude <reason>
#new-review`, which clears the cards and dispatches a fresh initial
review bypassing the skips. On an errored row it is the unblock (a
decision); on a row where no review ran at all (review:trivial, draft,
bot skip) it is a side action, "run a full review". In-progress rows
still wait.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* pr-review: audit fixes from the first live cycle

- Collision `overlap` now means two PRs touch the same or adjacent base
  lines (git's own conflict rule) instead of intersecting hunk spans.
  Checked against the base merge the cycle made on #21622: the new rule
  names exactly the three files that conflicted and none of the six
  sibling sweeps that merged cleanly a minute apart; the old rule got one
  of the three and flagged the siblings.
- Consolidate counts only members that overlap two or more others; a
  chain or a star is a merge order, not a sweep to fold together.
- A head moved only by base merge commits (what --unblock does) keeps its
  review CURRENT with a review:base-merged chip, instead of a blocked row
  asking for a no-op refresh.
- The route comment drops the em-dash and the queue's own proxy chips
  (desc:, brief:, blog:, cluster:, review:), which mean nothing to the
  reviewer it is addressed to.
- Do-next grammar for single rows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* pr-review: casing rides along in a link sweep; route rows can approve anyway

A five-file link sweep fell out of the link-only bar because one line also
fixed "Typescript" to "TypeScript", and the route row it became offered
no way to approve it although act.py accepts --stamp N --force on any
non-blocked row. Link-only now compares the masked lines case-insensitively,
and every route row carries an "approve anyway" secondary action.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: the lines box starts expanded

The quoted diff is the evidence a judge row is asking you to weigh, so
reading a row should not cost a click. Quotes now render open by default
(any count, not just single-judgment rows); the summary still folds them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: say who can answer a review, and whether approval merges

Two things the row was leaving implicit.

A changes-requested review only works on an author who reads it: a person,
or an agent bot (WorkPrentice, Copilot). pulumi-bot's content-review,
glow-up and regen lanes open a PR from a workflow run and will never
answer one, and neither will dependabot. Those rows carry author:generated
and offer "close it out" instead of "send back to author"; act.py refuses
--request-changes there without --force, --close N alone now closes with a
comment built from the row's judgments, and a send-back recommendation on
such a row becomes a close.

Approving is also two different things depending on the author, and the
button now says which: a bot row leads with "approve & merge", a
human-authored row with "approve, no merge" since merging is the author's
call. Each row carries the other choice as a second decision, and act.py
takes N:merge / N:no-merge per PR so a mixed batch is still one command.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: a disposition badge says whose call it is

"REFUTE" beside a finding read like the author had already refuted it. The
badge is the judge pass talking to the approver, so it now says so in the
first person: I'd refute, I'd accept, I'd call it n/a, I'd send it back
(I'd close it out on a row with no author to send it back to). Hovering
gives the plain-English version, and the board's dek says a badge is a
recommendation rather than something the author did. "already fixed" is
unchanged: the diff made that call, not me.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: badges say why a finding doesn't block, and approving records it

Two problems with the disposition badge. It was written in the first
person, which named a speaker the reader never asked about, and it implied
a decision the reader had no way to carry out.

The wording is now the reason the finding doesn't stop the merge: not a
real issue, fair and not blocking, doesn't apply, needs the author, nobody
to fix it, already fixed. The title says what approving does about it, and
a line under the findings says it in full, so it reads as context for the
row's buttons rather than a form to fill in.

And approving now actually carries it out: --stamp posts one
/resolve F<n> <disposition>: <why> per judged finding before the approval,
so the review's own state records why each finding closed instead of the
merge walking over it. A deferred judgment is not resolved -- that one is
what the send-back button is for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* skill: write down what makes a row "mine"

The rule was spread across the config section, the verdict table and the
reason-code list, so it could only be reconstructed by reading all three.
One section now states it: subjects from classify_path, the PR's domains
as their union, mine when that union intersects `me`, routing from the
matrix for the rest, and the two overrides (link-fixes, handed-off) plus
what --owner changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* routing: four teams, and lanes that come from the org chart

Three fixes, all the same mistake in different places: treating "I could
not read this" as "this does not exist."

A team lookup that 403s now routes to the team the config names, with a
route:team-unverified chip saying the check did not run, instead of
silently demoting every lane to its SLA person. That is why a session
without read:org saw route:no-team on every route row while the teams were
there all along.

A missing ~/.pr-review.yml now takes its lanes from the approver's GitHub
team memberships -- every routing team you are on contributes the subjects
its role owns -- and only claims every lane when those memberships cannot
be read. A first run needs no local setup to show route rows.

And blog routes to pulumi/docs-blog-review, which exists now, rather than
riding along with marketing. NOTE: this changes what Sentinel requires on
blog PRs, from a docs-marketing-review approval to a docs-blog-review one.
Its SLA escalate_to is cnunciato, same as marketing's -- change it if the
blog team wants someone else.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* queue: a change with no team gate is any approver's, and says so

The routing matrix already answers this: a mechanical docs or blog edit
resolves to no required role, so Sentinel's G3 passes with "no human
approval required (mechanical)" and there is no team to wait for. The
queue was routing those rows anyway, inventing a gate GitHub does not
have. They are now anyone's to approve, carrying gate:none.

And the chips that decide whether a row is on your board at all now read
as words rather than vocabulary: "no team approval needed", "link-only
sweep: yours", "team missing, routing to a person", "team not verifiable
from here". The code stays in the tooltip.

Also merges master into the branch (a merge, not a rebase, so a local
checkout of this branch keeps working).

Verified while here that Sentinel needs no change for the new blog team:
its G3 reads roles from the routing config and maps each to its team, so
a substantive blog PR now requires pulumi/docs-blog-review and a
mechanical one requires nobody. Nothing outside the config names a role.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: a manual, Do-next cards that say what they do, real tooltips

Three things a first-time reader could not work out from the page.

The board now carries its own manual, folded at the top, and the same
content ships as pr-review:references:reading-the-board: what the page is,
the four verdicts, how Do next relates to the rows, what a row's chips and
buttons mean, what a judgment badge claims, how the literal filters work,
and what the command at the bottom does.

Do-next cards were a batch command with no stated relationship to the rows
below them. Each card now states a fact naming its PRs, says in one
sentence what pressing it does ("Approves and squash-merges #21576, then
merges master into #21432 so it can follow"), and presses those rows' own
buttons rather than adding a parallel fragment. Pick a contrary decision on
one of a card's PRs and the card goes out, so the composed command cannot
contradict itself. A chain or a consolidation has no row button of its
own, so it claims its PRs instead and the same rule applies. "2 generated
rows" is gone: cards name PRs and say a workflow opened them.

And a chip's tooltip described nothing -- it repeated the chip. Every
reason code now has a sentence explaining what it means for that row, with
the raw code kept in parentheses so the queue stays greppable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: a chain card says what it is, not what to type

"C1: merge #21622 first, then unblock #21482" named the cluster before it
named the problem, and duplicated what the card's own action line already
says. The card now leads with why those PRs are stuck together -- they edit
the same lines in the same files, so they can only merge in order -- and
leaves the cluster id and the count in parentheses.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: tooltips that explain this chip, and a diff in the un-judged box

"review:base-merged" hovered to "The state of the pinned review: stale,
absent, still running, errored, or triage prose only" -- the family, not
the value, which is no help at all. Every code now describes itself:
base-merged says the head moved only because master was merged in, so the
review still describes the diff; absent says no review has run on this PR
at all. The ones carrying a number, an id, a path or a login interpolate
it, so warnings:2:F6,F7 reads "2 reviewer-check findings the review raised
and nobody has answered (F6,F7)".

Tooltips everywhere else too, since a tooltip that only some elements have
is its own puzzle: verdict chips and the tally cells say what the verdict
means, every button says what pressing it does and whether it merges,
filter chips explain the literal semantics, and both folds say what is
inside. A test now walks every title on a rendered board and fails any
that is empty, shorter than five words, or the same as the text it is
attached to.

And the un-judged box shows the diff. Before the judge step runs a finding
is still a claim about a line, so the box quotes that line out of the
patch -- the same evidence a judged row carries, minus the reasoning
nobody has written yet. It is also no longer called "Needs a call": it
says what it is, open findings nobody has decided, and how to get them
decided.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: a chain card marks the rows it covers

A batch card presses its rows' own buttons, so card and rows light
together. A chain cannot: it approves one PR and merges master into
another, which is not any single row button. It now marks those rows
instead -- they tint, and their action bar gets a "covered by Do next N"
tag -- so the card and the rows still read as one decision, and picking
something else on a covered row still puts the card out.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: whole findings, config marked as config, nothing hidden silently

Three things a reader could not get from the page.

The un-judged box repeated the row's own chips in its heading, so it read
as a second, disagreeing list; it now just says how many findings are open.
It also showed a 160-character excerpt of each finding, which is enough to
know a concern exists and not enough to know what it is. It now renders the
whole finding, markdown and all, from the review's own text.

A chip that is on the row because of your ~/.pr-review.yml now says so: a
dotted border, a ·cfg mark, and a tooltip that starts "YOUR SETTING".
That is link_fixes, stamp_max_lines and stale_date_days. shape:link-only
is not one of them -- it is a fact about the diff, true for every approver,
and it is what link_fixes acts on. The manual and the board's own help
panel now spell that distinction out.

And the filters hid work without saying how much: "stamp set" and "blocked"
default to off, so a queue could have five approvable PRs and nineteen
stuck ones with nothing on screen to say so. Every filter chip now carries
its count, and the blocked rows that have a mechanical unblock get their
own Do-next cards ("#21066, #21117, #21490 and 2 more are stuck behind a
merge conflict"), so being off the board no longer means being invisible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: don't cut a summary mid-word, don't imply findings that aren't there

Two bugs on the same row, #21670.

The one-line summary was a hard 200-character slice, so it ended
"...already tracked by an existin". It now cuts at the last sentence in
range, or failing that at a word boundary with an ellipsis.

And a judge row whose review found nothing still rendered the un-judged
box, headed as if findings were waiting to be read. When there are no open
findings the box now says so and names what is actually asking for a
decision -- "This row still needs a call: No review has run on this PR at
all" -- instead of implying a list nobody can see.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* routing: any review team may approve a link-only sweep

The queue said a link sweep was yours to approve while the merge gate still
wanted the lane's team. Two rules, one PR, opposite answers. The gate is
the one that moves.

`.github/review-routing.yml` grows a `link_only:` section, and it is set to
`approval: any-team`: a diff whose every changed line differs only in a
link still needs a human, but not a particular lane's human. The question
such a diff raises -- does the new target resolve, and does it still say
what the sentence claims -- is careful reading, not lane knowledge. The
Sentinel's G3 now accepts a member of any team in the matrix on those, and
the lane's role stays on the record either way. `approval: lane` keeps the
old behavior, and the mechanical bar is untouched: a link sweep is still
substantive, so it never merges unreviewed.

The bar itself now lives in sentinel.py and the queue imports it, because
the gate and the board have to agree on what a link sweep is. Those rows
carry gate:any-team, which makes them everyone's for a reason the board can
state, rather than because of a setting in one person's home directory.

Also: the un-judged findings box now surfaces the review's own stance on a
finding where it took one (its "Spurious" and "worth a look before you
approve" markers), instead of implying nobody has said anything about it.
What the judge step adds is a decision, and the box now says that.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: say why nothing reviewed a PR, and show a small diff in place

"review:absent" told you a review was missing and left the reason a
mystery, on a row where the reason is the whole story. The box now names
it from the label the pipeline acted on: trivial, frontmatter-only,
oversized, prose-flagged, draft, bot author.

And when a row has no findings, the diff is the thing to look at, so a diff
small enough to read (40 changed lines across at most 4 files, patches all
present) now renders in the box. Anything bigger links to the PR's files
view instead of pretending.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: the brief's own sentence is the summary, uncut

I had capped the row summary at 220 characters, which cut the reviewer's
guide's orientation sentence mid-thought on any PR whose change takes a
sentence to describe. That sentence exists precisely to orient an approver;
truncating it defeats the point, and a row is allowed to be two lines tall.

It now renders whole. The cap survives only for the PR-body fallback, at
600 characters and cutting on a sentence or a word, because a body's first
line is prose nobody wrote for this row. No row in the live queue is
truncated any more.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: the chain card lights the buttons it presses

It was tinting both rows and tagging them, because I had assumed a chain
had no row-level equivalent. It does: approve and merge the first link,
merge base into the next. The card now carries those two row buttons as its
targets, so clicking it lights exactly them and composes the same two
fragments, like every other batch card. No row tint anywhere.

Consolidate keeps the tag, and only the tag: its command carries a written
reason that no row button has, so there is nothing on the row to light.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: "render preview" said nothing; it screenshots the preview site

The label read like it rendered something, and the tooltip only restated
it. Every PR gets its own deployed copy of the site; this button opens each
page the PR changes on that copy and saves a full-page screenshot, for when
you want to look at the rendered page rather than the diff. The button now
says "screenshot the preview" and the tooltip says the rest, including that
it writes nothing to GitHub.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: reach the rendered page, the diff and an editor from the row

Three ways out of the board that previously meant a trip to GitHub and
back.

Each row now folds the PR's preview pages: every page the PR changes, on
its own deployed copy of the site, which is the same list pulumi-bot pins
in a comment. Looking at a rendered page is one click from the row.

The PR number grew two neighbours: "diff", straight to Files changed, and
"edit", the PR in the VS Code web editor for when reading a diff in a
browser tab is not enough.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: the reviewer's guide on the row, and one fewer button

The guide is written for an approver and the board was using one sentence
of it. Each row now folds the rest: what this PR changes, bullet by bullet;
what the review already verified so you needn't re-check it (facts,
mechanics, style); and links to the guide itself, the author card and the
evidence page. collect.py stores those two sections rather than making the
renderer parse markdown.

And the screenshot button left the board. With every changed page linked on
the row, a screenshot is a slower second way to see the same thing; it
earns its place only in the detail view, where the images embed. The
command is unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: one selection per decision, and a summary that isn't boilerplate

A generated PR body's first surviving prose line is often a machine note
to a machine, so a row summarized itself as "do not edit it". Prefer the
body's own "fixes applied" section (table row included) and skip wholly
italic template lines.

A decision that renders on both the compact row and the expanded card is
now painted from one keyed state, and a chain's lead row button presses
the whole chain, so the Do-next card and the row light together.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: the panels worth reading start open, and one lever moves them all

The reviewer's guide and the preview page links now default to expanded,
as does the collisions section; the manual and the per-verdict reasons
stay folded. An expand/collapse-all button beside the manual opens or
closes every panel at once for a sweep.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: the fold lever belongs to the board, not to the manual

Beside the manual it read as a control for the manual -- and it spared
the manual when collapsing. It now sits at the end of the filter bar,
says "expand every panel", and includes the manual in what it folds.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: a fold lever per row, and the manual keeps its own state

Each row now carries a fold button beside its PR links that closes or
opens that row's panels alone. The board-wide lever skips the manual:
the instructions are not part of the report.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: a chain card that can light, a row lever that follows, a header that explains itself

The chain card named an unblock button the follow-up row does not have
until it is actually stuck, so the card could never be lit; it now
presses what exists and claims the rest. The board-wide fold lever
relabels each row's own fold button. And the eyebrow says what the run
collected and where your lanes came from, instead of echoing flag names.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: the folds that lost their marker draw a chevron

A display:flex summary drops the browser's disclosure triangle, so the
collisions section and the manual read as plain headings. Both now draw
their own chevron, rotated when open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: a workflow PR has no reader for a send-back, not no fix

The author:generated tooltip and the no-author badge both implied
closing was the only move. The branch is still editable -- push to it,
apply the drafted fixes, or ask Claude on the PR. Only the send-back has
no audience.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: a chip that carries extra detail keeps its tooltip

checks:red:sentinel matched no help key, so the chip rendered bare. The
checks lookup now keys off the state and names the failing checks in the
sentence, and any known code with an unexpected value falls back to a
sentence rather than to nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* pr-review: planning is read-only, so stop asking permission to plan

The skill said to get a yes before --execute but never said not to ask
before the plan, so a run asked twice -- the first time about writing a
local file that touches nothing. One question, after the preview, about
doing the writes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: the fold lever broke every filter, and the stampable rows were hidden

The lever borrowed the filter chip's class, so the filter loop read
filters[undefined] and threw before apply() ran: chips did nothing and
hidden verdicts rendered anyway. Filter loops now select on
[data-filter] and the lever carries its own class.

Every verdict also starts lit. The stampable rows arrive pre-selected,
so the composed command acted on PRs the page did not show. The chip is
now called stampable, since it names rows rather than a saved set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* board: an unjudged finding reads like a judged one

The box dumped the review's whole paragraph and labelled it "the review
calls this spurious", which said nothing to an approver. A finding now
leads with the claim, carries a badge for where it stands (probably not
real / worth a look / nobody has ruled), one sentence of reasoning and
the diff lines, with the review's full note folded away.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* sentinel: a failed gh call says why, and the status comment is not a gate

A gh api failure raised with argv and nothing else, so the Actions log
carried the whole request body and no reason. _run now surfaces what the
API said. And the pinned status comment, written after every gate has
been decided, no longer fails the evaluation when the token cannot post
it -- the check run is the gate; the comment is a courtesy on top.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* Revert "sentinel: a failed gh call says why, and the status comment is not a gate"

This reverts b583a52. #21675 fixes the same failure properly:
the endpoint is governed by the pull_requests scope, not issues, so the
workflow needed pull-requests: write -- and it carries the same stderr
surfacing plus the get_team_membership fallout my version missed.

That fallout was a real bug here: get_team_membership catches
CalledProcessError to read a clean 404 as "not a member", and raising a
new exception type from _run would have turned every non-member into a
G3 error on every governed PR.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* staging: the auto lane dispatches and gets out

The unattended deploy sat in a staging-stack concurrency group watching
the run it had dispatched, for up to 45 minutes. GitHub cancels a
displaced pending run, so a second push turned the evidence producer
into a failing check on the PR -- the one thing that workflow's own
header says it must never be.

It now fires the existing "Build and deploy testing" workflow at the head
branch and exits, and that run resolves its own staging/pulumi-test-io
status. /deploy-staging still watches: someone is waiting on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* review-routing: drop the duplicate blog: keys the rebase left behind

Master's #21674 added blog: to teams: and sla:, and so did this branch.
The rebase applied both with no conflict, leaving duplicate YAML keys
whose last value happens to win with the same value -- correct by luck,
wrong to leave.

Also folds .pr-review-evidence/ and .pr-review-state/ into the ignore
block beside their siblings: record-evidence.py and sla-sweep.py write
them to the repo root, where an unignored file fails prettier --check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

* gitignore: say when each artifact is written, accurately

record-evidence.py writes .pr-review-evidence/ on every run -- "write two
local files, ALWAYS, regardless of whether S3 is reachable" is its stated
contract, not a fallback. Only sla-sweep.py's .pr-review-state/ depends on
no bucket being configured.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DmcivKmxnxxhAxSsQijVvf

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain:blog PR touches blog posts or customer stories review:no-blockers Claude review completed cleanly; outstanding is empty

Projects

None yet

Development

Successfully merging this pull request may close these issues.

/pricing/: support-tier row contradicts plan cards; two blog posts still advertise the retired 30-day self-hosted trial

2 participants