Retire references to the 30-day self-hosted trial in two blog posts - #21576
workprentice[bot] wants to merge 1 commit into
Conversation
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).
There was a problem hiding this comment.
🧹 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. |
There was a problem hiding this comment.
[style] wordiness — Single space; drops filler 'in order to'.
| 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. |
|  | ||
|
|
||
| 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/). |
There was a problem hiding this comment.
[style] wordiness — Plainer word, same meaning.
| 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/). |
Reviewer's guide v1 — not for the authorTip This is the reviewer's guide. Work through the 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:
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:
|
| 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 @claude <your point> #update-review re-adjudicates with your input.
Author action guide v1 — nothing blocks mergeNote 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 disagreeNothing to fix — this section is empty. ❓ Questions for youNo open questions for you. Style suggestionsOptional 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
📎 Full evidence: verification trail, investigation log, review history. Review v1 · updated 2026-09-12T14:16:36Z · head commit 7f58847 How to answerEvery 🚨 and ❓ item above needs one of these before merge:
The Please don't edit, hide, or delete this comment — it is the review's record. Full mechanics: CONTRIBUTING.md §AI-assisted contributions. |
|
Your site preview for commit 7f58847 is ready! 🎉 http://www-testing-pulumi-docs-origin-pr-21576-7f588474.s3-website.us-west-2.amazonaws.com Changed pages: |
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
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
* 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>
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) — themeta_descand 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-trialanchor #19434 established as canonical. I re-ran a sitewide sweep for30[- ]?day.{0,60}trialafter 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.yamland the comparison table left untouched.Validation
node ./scripts/lint/lint-markdown.js— 0 errors on both files./scripts/prettier.sh --check— clean🧠 This PR was created by workprentice.