Skip to content

Correct pre-built policy pack coverage, and enforce the allowlist against the registry - #21055

Merged
CamSoper merged 12 commits into
masterfrom
claude/policy-funnel-draft-context-uc1jpa
Aug 26, 2026
Merged

Correct pre-built policy pack coverage, and enforce the allowlist against the registry#21055
CamSoper merged 12 commits into
masterfrom
claude/policy-funnel-draft-context-uc1jpa

Conversation

@CamSoper

@CamSoper CamSoper commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

The list that decides which pre-built policy packs get a documentation page, data/policy_packs.yaml, is hand-maintained and was never checked against what Pulumi actually publishes. It had drifted in both directions: frameworks we ship for three clouds documented as AWS-only, and packs published privately to the org carrying public reference pages.

This corrects the drift and adds a check so it can't recur silently.

What's here

What Why
Corrected pack coverage on the pre-built packs table, the Policies and Insights overviews, and the per-cloud AWS/Azure/Google Cloud pages The generated reference pages under /docs/reference/pre-built-policy-packs/ have been correct all along; the hand-maintained summary pages understated them. A reader comparing the two would conclude we ship less than we do.
Removed hitrust-awsnative from the table, the AWS integrations page, and the allowlist Flagged in review by @danbiwer: it is private to the org, not a shipped pack, and docs automation had generated a public reference page for it. Since deleted from the org, which is why the nightly Policy Packs - Update Policies job currently fails on master — merging this clears it.
Repointed CIS Kubernetes GCP from cis-kubernetes-gcp to cis-kubernetes-google-cloud Same defect, opposite direction. policy-packs-internal publishes under cis-kubernetes-gcp, but that name is private, so the documented page was built from a pack customers cannot enable. The public pack is cis-kubernetes-google-cloud. Confirmed with @danbiwer, who is renaming it upstream so releases land on the public name. Both carry the same 50 policies and the slug is unchanged, so no page content or URL moves.
Registry check in scripts/fetch-policy-packs.js The three fixes above are the same bug three times, found by hand each time. /api/orgs/{org}/policypacks — which this script already reads — returns products and privately published packs indistinguishably. The registry listing distinguishes them with source: "pulumi", so the build can now enforce what a comment used to ask for.
Tests for that check, plus a repair to scripts/run-unit-tests.sh The check guards what reaches the public site and had no regression coverage. Two of the 15 cases fail against the previous commit. The runner turned out to be broken and unreferenced — it ran yarn --cwd components test against a directory that no longer exists — so it is repaired and wired into make test.
Two prose corrections in setting-up-for-success.md Pre-existing findings on untouched lines, surfaced by the pre-merge review: an unsourced compliance claim, and enforcement levels named "warning or error" rather than Pulumi's advisory / mandatory / remediate.

Coverage corrections

Framework Table said Actually ships
ISO/IEC 27001:2022 AWS AWS, Azure, Google Cloud (238 + 158 + 153 = 549 policies)
PCI DSS v4.0.1 AWS AWS, Azure, Google Cloud
NIST SP 800-53 AWS, Google Cloud AWS, Azure, Google Cloud
CMMC 2.0 absent AWS

How the check works

Every entry in data/policy_packs.yaml must come back from /api/registry/policypacks?orgLogin={org} with source: "pulumi", and every pack that does must have either a page or a recorded exemption. Both directions fail the build.

  • Not publisher. It is "pulumi" for every pack in the org, including the private ones, so filtering on it would admit exactly what this excludes. It is the field a reasonable person reaches for first, so there is a comment saying not to.
  • source is set by an admin command, not by whoever publishes the pack, which is what makes it safe to fail a build on.
  • Fails closed. An unreadable, changed, or paginated response stops the job rather than falling back to publishing unverified pages. Reading one page of a paginated listing is worse than not reading it: the forward check would fail packs that are fine, and the reverse check would silently miss the undocumented product it exists to catch.
  • undocumented: records products that deliberately get no generated page. Both pack: and why: are required, so the list cannot be used to quietly silence the check.

data/policy_packs.yaml remains the editorial layer — slugs, titles, meta descriptions, menu placement, framework grouping. The registry carries none of that (displayName is empty on every record). What changed is that it is no longer the source of truth for which packs are documented.

Verification

Run against the live listing for the pulumi org: 36 packs, 23 with source: "pulumi". All 22 documented packs plus the one recorded exemption account for exactly those 23, with no products left undocumented. make lint, make test-unit (15/15), and prettier --check pass.

Relationship to #21120

#21120 adds an Edition column to the same table and per-section feature: keys to data/policy_packs.yaml. That PR notes it left out the packs missing from the table pending confirmation they are GA — the registry query above is that confirmation, and those packs are what this PR adds. The two conflict on pre-built-packs.md and policy-packs/_index.md; this branch rebases onto #21120 once it lands, and the resolved table is this PR's coverage with that PR's column.

Follow-ups, not in this PR

  • Several pages still carry the pre-correction framework list and will read as incomplete once this lands: the "Policy as code" row across content/docs/iac/comparisons/*.md, and content/what-is/what-is-policy-as-code.md (L199).
  • pulumi/policy-packs-internal#211 records four packs that failed to publish in the 2026-07-09 release batch and were never retried, one of them a shipped pack. Unrelated to this diff.

The pre-built packs table and the pages that point at it were written when
ISO 27001, NIST, and PCI DSS shipped for fewer clouds than they do today, and
were never refreshed as coverage grew. The generated reference pages under
/docs/reference/pre-built-policy-packs/ (driven by data/policy_packs.yaml) have
been correct all along, so the summary surfaces were the only thing telling
readers we ship less than we do.

Reconciled against data/policy_packs.yaml and data/policy_pack_policies/:

- ISO/IEC 27001:2022 — listed as AWS-only; we ship AWS, Azure, and Google Cloud
  (238 + 158 + 153 = 549 policies).
- PCI DSS v4.0.1 — listed as AWS-only; we ship all three clouds.
- NIST SP 800-53 — listed as AWS and Google Cloud; we also ship Azure.
- CMMC 2.0 (AWS) — missing from the table entirely.
- HITRUST CSF — missing its AWS Native (aws-native provider) pack.

Also added ISO 27001 and CMMC to the framework lists on the Policies overview,
the policy packs overview, the Insights landing page, and the onboarding guide,
and filled in the missing packs on the per-cloud AWS, Azure, and Google Cloud
integration pages.

Every link added was checked against the allowlist that generates those pages.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvCEyksviZ5M9vNaZvAsdz
@CamSoper
CamSoper marked this pull request as ready for review August 21, 2026 20:33
@github-actions github-actions Bot added review:triaging Claude Triage is currently classifying the PR domain:docs PR touches technical docs and removed review:triaging Claude Triage is currently classifying the PR labels Aug 21, 2026

@unblocked unblocked 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.

✅ No issues found

About Unblocked

Unblocked has been set up to automatically review your team's pull requests to identify genuine bugs and issues.

📖 Documentation — Learn more in our docs.

💬 Ask questions — Mention @unblocked to request a review or summary, or ask follow-up questions.

👍 Give feedback — React to comments with 👍 or 👎 to help us improve.

⚙️ Customize — Adjust settings in your preferences.

@github-actions github-actions Bot added the review:in-progress Claude review is currently running label Aug 21, 2026
@github-actions

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Pre-merge Review — Last updated 2026-08-26T19:22:25Z

Tip

Summary: Nothing outstanding, and nothing left in ⚠️ either. 723be65 closes both remaining items, comment-only in both files: the reverse-direction paragraph moves back down to sit above accountedFor / undocumentedProducts (with the blank line restored, so staleExemptions gets its own preamble back), and cis-kubernetes-gcp is now recorded in the do-not-document block with the reason and the pointer to cis-kubernetes-google-cloud.

Two things worth saying about the fix beyond "it landed." First, the relocated paragraph was also reworded from "the same failure this PR was opened to fix" to "the same failure this check exists to catch" — an unprompted improvement, and the right one: a source comment that dates itself to a pull request is the next generation of the drift this change exists to stop. Second, you're right that my suggested header was wrong, and it contradicted my own arithmetic. It said "all 13 come back source: "private"" over a block that names 14 packs; the verification trail two rounds up had already reconciled the 13 as the 12 non-product names plus cis-kubernetes-gcp, with hitrust-awsnative deleted from the org and therefore absent from the listing entirely. Your rewording states which 13 and names the exception, which is what the arithmetic actually supports.

I re-ran the reconciliation against the file as it now stands: the "must NOT be documented" block names exactly 12 packs, plus cis-kubernetes-gcp = 13 private, and hitrust-awsnative is called out as not-in-the-org rather than counted. The new entry also agrees with the cis-kubernetes section comment 180 lines up (same direction, same upstream rename), which was the specific consistency the finding was worried about.

Review confidence:

Dimension Level Notes
mechanics HIGH
facts MEDIUM The live listing and @danbiwer's confirmation of the pack split remain author-reported, not reachable from here (no token). The counts reconcile exactly against the repo, which is strong corroboration but not the same as running it.
code correctness HIGH This push is comment-only in both files, so nothing executable changed. CI ran Run unit tests at step 14 on this head — before Build and deploy at 15 — and it passed.
cross-sibling consistency HIGH
Investigation log
  • Cross-sibling reads: 3 of 3 siblings
  • External claim verification: 33 of 39 claims verified (1 unverifiable, 0 contradicted this round) · 4 specialists (numerical, cross-reference, capability, framing); 0 cross-specialist corroborations · routed: 11 inline, 16 Pass 1, 2 Pass 2, 3 Pass 3.
  • Cited-claim spot-checks: not run (no cited claims)
  • Frontmatter sweep: ran on body + meta_desc
  • Temporal-trigger sweep: ran (the "Verified against the live listing on 2026-08-25" stamps and the "(2026-08-25)" stamp in the cis-kubernetes section comment re-checked against the reported output; the reworded header keeps the same date and the same reported numbers)
  • Comment-relocation check: ran — diffed auditAllowlist against the previous head. The four-line reverse-direction paragraph now sits directly above const accountedFor, staleExemptions has its own two-line preamble with a blank line separating the two blocks, and no other line in the function changed. Every paragraph in the function abuts the check it describes again.
  • Non-product-block arithmetic re-run: ran — 12 names in the "must NOT be documented" list (approved-component-versions-badtestpack-aws-api), plus cis-kubernetes-gcp = the 13 reported private; hitrust-awsnative is named as the exception (deleted from the org), so it is described but not counted. Matches the reconciliation recorded on 2026-08-25, which the previous suggestion's header did not.
  • Cross-comment consistency: ran — the new cis-kubernetes-gcp entry and the cis-kubernetes section comment at L110-116 agree on direction (-gcp private, -google-cloud the product) and on the upstream rename.
  • Code execution: not run locally (node invocation unavailable in this environment). Not load-bearing this round: the push is comment-only. GitHub Actions ran Run unit tests at step 14 of job 98293179231 on 723be65, ahead of Build and deploy at 15, and it completed successfully.
  • Lazy-require sweep: ran — unchanged by this push; yaml still has exactly one reference, inside main().
  • CI-wiring sweep for make test-unit: ran previously; unchanged.
  • Merge-resolution audit for 143a0a00 (against Mark compliance policy packs as Business Critical in docs and pricing #21120): ran on an earlier round.
  • Allowlist arithmetic re-run: ran — 22 pack: entries under sections:, 22 files in data/policy_pack_policies/, 1 undocumented: entry, 8 feature: keys against 8 sections. Unchanged by this push.
  • Code-examples checks: not run (no fenced code blocks in content files)
  • Editorial-balance pass: not run (not under content/blog/)
🚨 Outstanding ⚠️ Low-confidence 💡 Pre-existing ✅ Resolved
0 0 0 2

🔍 Verification trail

39 claims extracted · 33 verified · 1 unverifiable · 4 contradicted · 1 detector findings
  • L23 in content/docs/administration/get-started/setting-up-for-success.md "The infrastructure hosting Pulumi Cloud aligns with IT security standards including SOC 1/SSAE 16/ISAE 3402, SOC 2, SOC 3, FISMA, FedRAMP, DOD SRG Levels 2…" → ✅ verified (evidence: the live Pulumi docs page states the list verbatim; source: https://www.pulumi.com/docs/administration/onboarding-guide/setting-up-for-success/)
  • L23 in content/docs/administration/get-started/setting-up-for-success.md "Pulumi Cloud is SOC 2 Type II certified." → ✅ verified (evidence: 13587ab1 drops the unsupported "and AWS-reviewed for compliance best practices" half; what remains is exactly what the cited page asserts — "The Pulumi platform employs industry best practices for security and is SOC 2 Type II certified."; source: repo:content/security/_index.md)
  • L25 in content/docs/administration/get-started/setting-up-for-success.md "Users can write custom Pulumi Policies for their specific industry or enterprise requirements." → ✅ verified (evidence: "Need custom policies? Learn to write custom policy packs in TypeScript, JavaScript, Python, or OPA (Rego). Create organization-specific rules tailored to your requirements."; source: repo:content/docs/insights/policy/_index.md)
  • L25 in content/docs/administration/get-started/setting-up-for-success.md "Pulumi Policies includes hundreds of out-of-the-box policies for AWS, Azure, Google Cloud, and Kubernetes." → ✅ verified (framing: source lists the frameworks/providers covered by pre-built packs; the "hundreds" aggregate is reasonable given 9 frameworks × up to 3 clouds; source: repo:content/docs/insights/policy/policy-packs/pre-built-packs.md)
  • L27 in content/docs/administration/get-started/setting-up-for-success.md "Configure each policy as advisory, mandatory, or remediate." → ✅ verified (evidence: 13587ab1 replaces the invented "warning or error levels" with Pulumi's actual named levels. authoring.md:93-95 — "The enforcement level can be "advisory", "mandatory", "remediate", or "disabled""; omitting disabled is correct here, since it is the off switch rather than a level you configure a policy as; source: repo:content/docs/insights/policy/policy-packs/authoring.md)
  • L27 in content/docs/administration/get-started/setting-up-for-success.md "Pulumi Policies can be applied flexibly across projects, for example so that GDPR rules only apply to infrastructure in European regions." → ✅ verified (evidence: "Policy groups organize one or more policy packs and apply them to specific stacks or cloud accounts."; source: repo:content/docs/insights/policy/policy-groups.md)
  • L27 in content/docs/administration/get-started/setting-up-for-success.md "Pulumi Policies can be applied flexibly across projects, for example so that GDPR rules apply only to infrastructure in European regions." → ✅ verified (framing: source describes the general scoping capability; the GDPR example is a specific instantiation; source: repo:content/docs/insights/policy/policy-groups.md and repo:content/docs/insights/policy/_index.md)
  • L73 in content/docs/insights/_index.md "Pulumi Insights provides ready-made (pre-built) compliance policy packs for the ISO 27001 standard requiring no code." → ✅ verified (evidence: the Pre-built Policy Packs reference page lists "ISO/IEC 27001:2022" with packs for AWS, Azure, and Google Cloud; source: repo:content/docs/insights/policy/policy-packs/pre-built-packs.md)
  • L54 in content/docs/insights/policy/_index.md "Individual Pulumi Policies validate infrastructure configuration, for example enforcing that VMs must use approved instance types." → ➖ not-a-claim — an illustrative example of the author's own documented concept, not a falsifiable external assertion (source: repo:content/docs/insights/policy/_index.md)
  • L55-56 in content/docs/insights/policy/_index.md "Pulumi Policy groups let users enforce stricter policies in production and more permissive policies in development environments." → ✅ verified (evidence: "the usual way to express this is with a separate policy group for each environment tier: one group covering lower-environment stacks where the packs are advisory, and…"; source: repo:content/docs/insights/policy/policy-groups.md)
  • L118 in content/docs/insights/policy/_index.md "Pre-built Pulumi policy packs can be enabled directly from Pulumi Cloud with no code required." → 🤝 matches content/docs/insights/policy/policy-packs/_index.md (which states the identical claim about the same linked page: "You enable them from Pulumi Cloud without writing any code.")
  • L27 in content/docs/insights/policy/policy-packs/_index.md "Pulumi's pre-built policy packs cover common compliance frameworks, including CIS, PCI DSS, HITRUST, NIST, ISO 27001, and CMMC." → ✅ verified (evidence: the sibling page's "Available policy packs" table lists exactly these — CIS 8.1, CIS Kubernetes, CMMC 2.0, HITRUST CSF 11.5, ISO/IEC 27001:2022, NIST…; source: repo:content/docs/insights/policy/policy-packs/pre-built-packs.md)
  • L29 in content/docs/insights/policy/policy-packs/_index.md "Users can test a custom policy pack locally with pulumi preview --policy-pack before publishing it to Pulumi Cloud." → ✅ verified (evidence: the generated CLI reference documents --policy-pack strings Run one or more policy packs as part of this update; source: repo:content/docs/iac/cli/commands/pulumi_preview.md)
  • L39 in content/docs/insights/policy/policy-packs/pre-built-packs.md "Pulumi provides a pre-built CMMC 2.0 policy pack that supports AWS." → ✅ verified (framing: source confirms CMMC is a pre-built framework and that pre-built packs generally support AWS/Azure/GCP; source: https://www.pulumi.com/docs/insights/policy/policy-packs/)
  • L39 in content/docs/insights/policy/policy-packs/pre-built-packs.md "The CMMC 2.0 policy pack enforces CMMC 2.0 practices for AWS resources, helping defense-industrial-base organizations meet Department of Defense…" → ✅ verified (framing: source confirms CMMC exists as a pre-built pack category; the DIB/DoD framing is a standard, accurate description; source: https://www.pulumi.com/docs/insights/policy/policy-packs/)
  • L40 in content/docs/insights/policy/policy-packs/pre-built-packs.md "The HITRUST CSF 11.5 pack is available for AWS, Azure, and Google Cloud." → ✅ verified (evidence: after the 93d5904d removal the row links exactly three packs, matching the three hitrust entries in data/policy_packs.yaml and the three remaining data files; no aws-native reference survives; source: repo:data/policy_packs.yaml, repo:data/policy_pack_policies/)
  • L40-43 in content/docs/insights/policy/policy-packs/pre-built-packs.md "The ISO/IEC 27001:2022 policy pack enforces ISO/IEC 27001:2022 Annex A controls across multiple cloud providers…" → 🤷 unverifiable (evidence: the verifier followed a source_hint pointing at the HITRUST page rather than the ISO 27001 one, then tried an inaccessible private repo. Triaged below — against the actual source of truth the wording is correct; source: repo:content/docs/insights/policy/policy-packs/pre-built-packs.md)
  • L80-81 in content/docs/integrations/clouds/azure/_index.md "There exists a pre-built policy pack named 'NIST 800-53 for Azure' located at /docs/reference/pre-built-policy-packs/nist/azure/." → ✅ verified (evidence: matches the sibling AWS page's identical pattern; source: content/docs/integrations/clouds/azure/_index.md L80 and content/docs/integrations/clouds/aws/_index.md L89)
  • L83 in content/docs/integrations/clouds/azure/_index.md "There exists a pre-built policy pack named 'ISO/IEC 27001 for Azure' located at /docs/reference/pre-built-policy-packs/iso-27001/azure/." → ✅ verified (evidence: data/policy_pack_policies/iso-27001-azure.json exists and contains policies whose framework.name is "ISO/IEC 27001" (version 2022) targeting Azure resources; source: repo:data/policy_pack_policies/iso-27001-azure.json)
  • L75-76 in content/docs/integrations/clouds/gcp/_index.md "Pulumi provides a pre-built policy pack implementing NIST 800-53 for Google Cloud." → ✅ verified (evidence: the GCP integrations page lists it alongside the other pre-built packs, matching the sibling AWS pattern; source: content/docs/integrations/clouds/gcp/_index.md L75)
  • L78 in content/docs/integrations/clouds/gcp/_index.md "Pulumi provides a pre-built policy pack implementing ISO/IEC 27001 for Google Cloud." → ✅ verified (framing: claim restates the linked doc title/existence, corroborated by the consistent sibling pattern; source: content/docs/integrations/clouds/gcp/_index.md L78, content/docs/integrations/clouds/aws/_index.md L93)
  • L150-152 in scripts/fetch-policy-packs.js "Verified against the live listing for the pulumi org on 2026-08-25 (36 packs: 23 pulumi, 13 private)." → ✅ verified (evidence: @CamSoper's live curl, and the counts reconcile exactly against this repo — 23 products = the 21 documented packs that are products + the 2 undocumented: exemptions; 13 private = the 12 non-product names recorded in the do-not-document block + cis-kubernetes-gcp; 21 + 1 = the 22 sections: entries. Every name has to be in the right column for that identity to close; source: mention body on Correct pre-built policy pack coverage, and enforce the allowlist against the registry #21055, repo:data/policy_packs.yaml)
  • L153-154 in scripts/fetch-policy-packs.js "The response is ListPolicyPacksResponse … a required policyPacks array plus an optional continuationToken for paging." → ✅ verified (evidence: ListPolicyPacksResponse declares exactly policyPacks (sole required member) and continuationToken ("can be used to fetch the next page of results. If nil, there are no more results available."); source: https://api.pulumi.com/api/openapi/pulumi-spec.json)
  • L156-175 in scripts/fetch-policy-packs.js "parseRegistryListing pins the policyPacks envelope and refuses a paginated response outright." → ✅ verified (evidence: const rows = body?.policyPacks with an Array.isArray guard, then a body.continuationToken throw before any row is read. The bare-array and items tolerances are gone; source: repo:scripts/fetch-policy-packs.js L181-192)
  • L200-207 in scripts/fetch-policy-packs.js "A listing with rows but no products is a shape problem, never an allowlist problem." → ✅ verified (evidence: if (rows.length && !products.size) throws inside parseRegistryListing, i.e. before auditAllowlist can reach the forward check, so the shape error wins the race it used to lose; and nonProducts.set(row.name, row.source ?? "(no source field)") keeps present-without-source distinguishable from "absent from the listing"; source: repo:scripts/fetch-policy-packs.js L194-207)
  • L240-246 in scripts/fetch-policy-packs.js "Every entry under undocumented: needs both a pack: and a why:." → ✅ verified (evidence: undocumented.filter((e) => !e?.pack || !String(e?.why || "").trim()) throws on both the missing-why: and bare-string forms; alsoDocumented rejects a pack that also has a page; staleExemptions warns on an exemption that is no longer a product; source: repo:scripts/fetch-policy-packs.js L240-265)
  • L254-256 in scripts/fetch-policy-packs.js "A pack that loses product status leaves a permanent, invisible exemption behind -- which is how hitrust-awsnative survived as long as it did." → ✅ verified (evidence: 723be65 restores this paragraph as the sole preamble to staleExemptions, the check it describes — undocumented.map((e) => e.pack).filter((p) => !products.has(p)), which warns rather than throwing, matching "says something rather than throwing". hitrust-awsnative's history is recorded in data/policy_packs.yaml's do-not-document block; source: repo:scripts/fetch-policy-packs.js, repo:data/policy_packs.yaml)
  • L265-268 in scripts/fetch-policy-packs.js "...and the other way round: a pack we publish as a product but do not document is the same failure this check exists to catch, just pointing the other way." → ✅ verified (evidence: the paragraph now sits directly above const accountedFor / undocumentedProducts, which is exactly the reverse-direction check it describes — products not in sections: and not in undocumented: throw. The rewording from "this PR was opened to fix" to "this check exists to catch" also removes the file's one reference to a pull request, which would have aged out of meaning; source: repo:scripts/fetch-policy-packs.js L265-278)
  • L26-29 in data/policy_packs.yaml "the obligation also runs the other way … This file is no longer just an allowlist -- it is a claim about the whole product set, and the check holds it to that." → ✅ verified (evidence: the third THE RULE bullet now matches the undocumentedProducts throw in auditAllowlist; the header no longer understates what the file does; source: repo:data/policy_packs.yaml L26-29, repo:scripts/fetch-policy-packs.js L265-278)
  • L258-259 in data/policy_packs.yaml "Both keys are required: an entry with no why: fails the check rather than silencing it." → ✅ verified (evidence: the unexplained throw above; the comment and the code now agree; source: repo:scripts/fetch-policy-packs.js L240-246)
  • L283-288 in data/policy_packs.yaml "Verified against the live listing on 2026-08-25, which returned 13 packs as source: "private" -- cis-kubernetes-gcp plus the 12 named further down. hitrust-awsnative is the exception: it is no longer in the org at all." → ✅ verified (evidence: the "must NOT be documented" block names exactly 12 packs, and 12 + cis-kubernetes-gcp = the 13 private records in the reported listing; hitrust-awsnative was deleted from the org by @jkodroff, so it cannot appear in a 36-record listing that already reconciles to 23 + 13. This supersedes the previous "all 12 come back private" wording, which counted the block's named packs rather than the listing's private records; source: mention body on Correct pre-built policy pack coverage, and enforce the allowlist against the registry #21055, repo:data/policy_packs.yaml L283-305)
  • L289-294 in data/policy_packs.yaml "cis-kubernetes-gcp: the name policy-packs-internal publishes under … but never a product. Document cis-kubernetes-google-cloud instead." → ✅ verified (evidence: agrees in direction and detail with the cis-kubernetes section comment at L110-116 (-gcp built and published privately, -google-cloud the hand-uploaded public pack) and with the reported listing, which returns -gcp as source: "private" and -google-cloud as source: "pulumi"; source: repo:data/policy_packs.yaml, mention body on Correct pre-built policy pack coverage, and enforce the allowlist against the registry #21055)
  • L4-7 in scripts/run-unit-tests.sh "This previously ran yarn --cwd components test, against a components/ directory that no longer exists." → ✅ verified (evidence: ls components → "No such file or directory", and before this commit the script had no referent anywhere in the repo — no Makefile target, no workflow, no package.json script; source: repo:scripts/run-unit-tests.sh, repo-wide grep for run-unit-tests)
  • L93-95 in .github/workflows/pull-request.yml "Runs before the build because it needs nothing installed -- make ensure begins with clean, so anything installed ahead of it is thrown away." → ✅ verified (evidence: Makefile:25 declares ensure: clean, and scripts/clean.sh:5 is rm -rf node_modules; make ci_pull_request reaches yarn install only through that chain, so a pre-install step would in fact be discarded. Both halves of the comment — the placement rationale and the "needs nothing installed" premise — hold on this head; source: repo:Makefile, repo:scripts/clean.sh, repo:scripts/ensure.sh)
  • L283-285 in scripts/fetch-policy-packs.js "Required here rather than at the top so the pure helpers this module exports stay importable with no node_modules … Nothing above main() needs it." → ✅ verified (evidence: yaml has exactly one reference in the file, yaml.load one line below the require and inside main(); the only other top-level requires are fs and path, both Node builtins; module.exports exports only parseRegistryListing, auditAllowlist, latestVersionTag, unclosedFences, none of which touch YAML. Executed proof: CI loaded the module and ran the suite before yarn install; source: repo:scripts/fetch-policy-packs.js, job 98293179231 step 14)
  • L103 in data/policy_packs.yaml "cis-kubernetes-gcp is published as a Pulumi product (asserted by documenting it under THE RULE)." → ❌ contradicted (evidence: the live listing returns cis-kubernetes-gcp with source: "private". THE RULE requires source: "pulumi" for any documented pack, so the forward check's notProducts filter includes it and auditAllowlist throws. Fixed in ea95624; source: mention body on Correct pre-built policy pack coverage, and enforce the allowlist against the registry #21055, repo:scripts/fetch-policy-packs.js)
  • L104-106 in data/policy_packs.yaml "Both are published, but only this one is still built by policy-packs-internal; the -google-cloud name is a stale earlier publish that no longer receives updates." → ❌ contradicted (evidence: the live listing has it the other way round — cis-kubernetes-google-cloud is source: "pulumi" at v1.0.0 and cis-kubernetes-gcp is source: "private". @CamSoper confirms the comment "has it backwards"; fixed in ea95624; source: mention body on Correct pre-built policy pack coverage, and enforce the allowlist against the registry #21055)
  • L267-270 in data/policy_packs.yaml "cis-kubernetes-google-cloud: A stale earlier publish, superseded by cis-kubernetes-gcp." → ❌ contradicted (evidence: same inversion. The why: described the exempted pack as the superseded one, when it is the only one of the pair the registry returns as a product. The entry was deleted in ea95624 rather than corrected, which is the right shape since the pack now has a page; source: mention body on Correct pre-built policy pack coverage, and enforce the allowlist against the registry #21055)
  • L155 in Makefile "Unit tests: the components suite plus the scripts/*.test.js files." → ❌ contradicted (evidence: the same commit rewrote scripts/run-unit-tests.sh to run node --test scripts/*.test.js and nothing else, precisely because components/ no longer exists. Fixed in 32b2cde0; source: repo:Makefile, repo:scripts/run-unit-tests.sh)
  • L92 in .github/workflows/scheduled-test.yml — the new make test-unit target does not run on pull requests → 🚩 flagged (infra: gate excludes the changed paths — the only caller of make test is guarded by steps.gate.outputs.programs != 'false', and the gate greps the changed-file list for ^static/programs/ only; make ci_pull_request runs lint, not test. Resolved by wiring make test-unit into pull-request.yml's buildSite.)

🚨 Outstanding in this PR

No outstanding findings.

⚠️ Low-confidence

No low-confidence findings.

📋 Triaged verifier findings

I double-checked these and realized they weren't real findings — click to expand
  • [L44] content/docs/insights/policy/policy-packs/pre-built-packs.md"The ISO/IEC 27001:2022 policy pack enforces ISO/IEC 27001:2022 Annex A controls across multiple cloud providers…" — verdict: unverifiable. Mis-sourced: the check followed a hint pointing at the HITRUST page rather than the ISO 27001 one, and then tried a private repo it couldn't reach. Against the actual source of truth the widened "multiple cloud providers" wording is correct: data/policy_packs.yaml defines iso-27001 packs for aws, azure, and google-cloud, and all three data files are present.

  • [L119] data/policy_packs.yaml — flagged that renaming the pack without an aliases: entry would break the live URL. Verdict: not a finding. The generated path comes from slug:, not pack:, and ea95624 leaves slug: google-cloud untouched — /docs/reference/pre-built-policy-packs/cis-kubernetes/google-cloud/ is byte-identical before and after. _content.gotmpl confirms it reads .slug for the page path. No alias needed, and the author said so first.

  • [L42] content/docs/insights/policy/policy-packs/pre-built-packs.md — flagged the CMMC row's hand-written "Business Critical" cell as unsourced merge-resolution invention. Verdict: not a finding. It's consistent with both sides: cmmc-aws carries feature: compliance-policy-packs (available_from: business-critical), and Mark compliance policy packs as Business Critical in docs and pricing #21120's own business-critical availability string at pulumi_pricing.yaml:604 already listed CMMC. The row was the only thing missing.

💡 Pre-existing issues in touched files (optional)

No pre-existing findings.

Separately, and outside this PR's scope: several other pages still carry the pre-correction framework list this PR is fixing, and will read as incomplete once these changes land — content/docs/iac/comparisons/terraform/_index.md (L54) and the parallel "Policy as code" table row in the other content/docs/iac/comparisons/*.md pages ("CIS, HITRUST, NIST, and PCI DSS"), and content/what-is/what-is-policy-as-code.md (L199, "Pre-built compliance packs (CIS, NIST, PCI DSS, HITRUST)"). Recorded as a follow-up in the PR body. The marketing and blog pages that repeat the list are historical and don't need updating.

✅ Resolved since last review

  • [L254-260] scripts/fetch-policy-packs.js — resolved in 723be65. The four-line reverse-direction paragraph moves down to sit directly above const accountedFor / undocumentedProducts, the check it actually describes, and staleExemptions gets its own two-line preamble back with a blank line between the blocks. I diffed the function against the previous head: those are the only changed lines, and every paragraph in auditAllowlist now abuts the check it introduces.

    You also reworded it on the way past — "the same failure this PR was opened to fix" became "the same failure this check exists to catch." Worth calling out because it wasn't asked for and it's the same class of problem one step further out: a comment anchored to a pull request stops meaning anything about six months after the PR merges, and this file's whole thesis is that comments have to keep describing the code. The new wording is durable.

  • [L283-301] data/policy_packs.yaml — resolved in 723be65, and improved on the suggestion. cis-kubernetes-gcp is now recorded in the do-not-document block with the reason ("the name policy-packs-internal publishes under … but never a product"), the pointer to cis-kubernetes-google-cloud, and a note that the upstream rename will stop it being published at all. That closes the gap the finding was about: an auditor diffing repo-against-org now finds the name in the list they'd actually consult, rather than only in a section comment 180 lines up. The two now agree on direction and on the rename, which I checked explicitly.

    concede: the count in my suggested header was wrong, and the author's rewording is the correct one. My version said "all 13 come back source: "private"" over a block that names 14 packs — hitrust-awsnative isn't private, it's been deleted from the org entirely, so it's described here but not among the 13. That contradicted my own arithmetic from the 2026-08-25 round, which had already reconciled the 13 as the 12 non-product names plus cis-kubernetes-gcp. The shipped header says which 13 and names the exception. Re-checked against the file as it stands: the "must NOT be documented" list is exactly 12 names, 12 + 1 = 13, and no name in the block is unaccounted for.

📜 Review history

  • 2026-08-21T20:46:19Z — All ten newly linked pre-built policy pack pages verified against data/policy_packs.yaml; no blockers. Two verifier findings triaged as spurious/mis-sourced, and two pre-existing inaccuracies noted on untouched lines of setting-up-for-success.md. (69af652)

  • 2026-08-24T16:37:34Z — Re-reviewed after fix push (2 new commits, 93d5904). acd985ff merged master, which renamed setting-up-for-success.md into get-started/; the branch's edit survived the rename and the two pre-existing findings were re-anchored to L23/L27 at the new path. 93d5904d removed the HITRUST AWS Native pack; re-verified the removal is complete across content, data, and redirects, and moved the previously-triaged [L92-94] finding to ✅ Resolved with a concession — the verifier's original contradicted verdict was correct and the triage that overruled it confounded "built in policy-packs-internal" with "shipped to customers". facts confidence downgraded from HIGH to MEDIUM to reflect that limit. Claim set now 21: the AWS Native claim's line no longer exists, replaced by a new check that the HITRUST row's three remaining links match the allowlist. 0 outstanding.

  • 2026-08-24T21:44:57Z — Re-reviewed after fix push (1 new commit, cd00d9a), which brings scripts/fetch-policy-packs.js into the diff and opens the infra lane for the first time on this PR. Nothing outstanding was disputed and nothing needed re-triage; the content half is unchanged and data/policy_packs.yaml reads 22 packs against 22 data files, matching @CamSoper's note — no claim in this review depends on the earlier 23-pack state. Since the live registry call can't run without a token, I verified the new parser against the published Pulumi Cloud OpenAPI spec (https://api.pulumi.com/api/openapi/pulumi-spec.json, fetched at review time — the same document scripts/fetch-openapi-spec.sh renders /docs/reference/cloud-rest-api/ from). That resolved the envelope question the commit left open — ListPolicyPacksResponse requires policyPacks and carries a continuationToken, so the listing pages and items is not a defined shape — and surfaced that the only documented query parameters are access and orgLogin, with no way to request a second page. Three new 🚨 findings: the unhandled pagination (a partial first page fails legitimate packs with an error that reads like a true positive), the .some() shape guard that lets schema drift throw the allowlist error instead of the shape error, and a stale "two deliberate exceptions" count in data/policy_packs.yaml introduced by 93d5904d. One ⚠️: neither pasted record is an allowlisted pack — aws-organizations-tag-policies is a documented exception and approved-component-versions-bad is on the do-not-document list — so no evidence here shows a documented pack returning source: "pulumi", and the spec's field description still claims only "private" is supported. Answered @CamSoper's three questions in the findings: pin policyPacks (question 1, the spec pins it); fail-closed reads right and the #docs-ops notify job earns it, with the caveat that it guards adding a page and not retracting one (question 2); and the publisher reasoning is correct as written and documented in the right place (question 3). Claim set now 25. code correctness added at MEDIUM. Also could not locate the unit tests the mention describes — no test file in the diff, no reference to fetchRegistryProducts outside the script, and scripts/run-unit-tests.sh covers only components. (cd00d9a)

  • 2026-08-25T16:39:38Z — Re-reviewed after fix push (1 new commit, e779449), which adds the reverse direction of the registry check and the undocumented: key. Nothing was disputed. [L254] (stale "two deliberate exceptions" count) → ✅ Resolved: the commit deletes the sentence rather than correcting the number, splitting the prose list into the machine-read undocumented: key for the two product omissions and a comment for hitrust-awsnative, which was never a product. Verified both why: values against the repo (aws-organizations-tag-policies/aws.md exists as a hand-written page; cis-kubernetes-google-cloud is corroborated as a superseded-but-published pack by the pre-existing comment at L100-102). The other two 🚨 are untouched by this commit and stand, with the pagination finding strengthened: the two checks now read the same possibly-partial products set and fail in opposite directions off it, so a short first page fails documented packs that are fine and silently hides an undocumented product — the reverse check fails open on exactly the input it was written to catch. Two new 🚨. (1) undocumented: is unvalidated — the check reads only .pack, so - pack: foo with no why: silences it as effectively as a justified exemption, the bare-string form yields undefined and mis-fires, an entry duplicated in sections: goes unnoticed, and nothing else covers the file (no scripts/lint/ rule reads data/policy_packs.yaml; scripts/run-unit-tests.sh still only runs components). The undeclared check 25 lines up is the precedent for the fix. (2) The reverse check makes the hand-written 12-pack "not built here" comment at L277-283 load-bearing without wiring it into the check: one product hiding in that list turns the first post-merge nightly red, with all 22 packs' data frozen behind it, since the gate precedes the fetch and the workflow's Commit changes step doesn't run on a non-zero exit. Answered @CamSoper's coupling question there — keep it failing, not a bare warning (a console.warn in a green 07:30 cron has the same reader count as silence, and notify only fires on failure()), but the asymmetry is real: the forward check's fail-closed cost buys "don't publish a private pack," while the reverse check's buys "a page is missing," and stopping the sync doesn't produce the page. Suggested shape: keep the non-zero exit, move the report to the end of main(), and adjust the workflow's commit/PR steps to run on failure — noting that without the workflow change the move gains nothing, so leaving it blocking is defensible if the curl is run first. Confirmed the undocumented: consumer question: no consumer was missed — _content.gotmpl ranges .sections only (L28), and the file has exactly two readers repo-wide. One curl against /api/registry/policypacks?orgLogin=pulumi, given in the L266-283 finding, would retire three of these at once (day-one greenness of the reverse check, source: "pulumi" on documented packs, and pagination); the simulated-listing matrix in the commit message is the right matrix but is still not committed as a test. Claim set now 29 (the L254 claim's line no longer exists; five new claims from the commit's comments and the new check). Confidence unchanged. (e779449)

  • 2026-08-25T17:59:37Z — Re-reviewed after fix push (1 new commit, 13587ab). All four 🚨 and both ⚠️ resolved, plus both 💡 pre-existing items fixed in-file — 8 to ✅, 0 carried over. Nothing was disputed; @CamSoper ran the curl I'd asked for against the live pulumi org (36 packs, 23 pulumi, 13 private) and it retired the three findings that were waiting on it. Verified the reported counts reconcile exactly against the repo — 23 products = the 21 documented packs that are products + the 2 undocumented: exemptions, and 13 private = the 12 recorded non-product names + cis-kubernetes-gcp — a tight enough identity that no name can sit in the wrong column without breaking it, which is why I've marked the previously-unverifiable 12-pack assertion and the source: "pulumi" assumption as verified on author-reported evidence rather than leaving them open. Code side: parseRegistryListing pins the policyPacks envelope and throws on a present continuationToken; the .some() guard is replaced by rows.length && !products.size, and because it lives in parseRegistryListing it now fires strictly before the allowlist error it used to lose to; nonProducts stores "(no source field)"; the undocumented: list gains unexplained / alsoDocumented / staleExemptions (the last being the stale-exemption asymmetry I'd raised as an aside). 15 node:test cases now cover both gate directions and every shape branch, reachable without a token via the require.main === module guards and the pure-function split — read, not executed (node unavailable here), with each assertion checked against the literal error string it matches. Four new 🚨. (1) The headline: the curl shows cis-kubernetes-gcp is source: "private" while L103 documents it, so the forward check throws on this branch's own data — the first post-merge nightly exits non-zero at the fetch step, never reaches Commit changes (no if: always()), opens no PR, and pages #docs-ops nightly, with all 22 packs' JSON frozen behind it. I agree the product status isn't a code fix, but Mark compliance policy packs as Business Critical in docs and pricing #21120 is a pricing/edition change that doesn't alter which pack the registry returns as a product, so the deferral target doesn't close this; laid out the three resolution shapes. Also recorded that /docs/reference/pre-built-policy-packs/cis-kubernetes/google-cloud/ is live now, generated from a private pack — a second hitrust-awsnative still on the site. (2) The cis-kubernetes inversion is left unfixed in two comments the same commit stamps as verified: L104-106, and — the one that matters — the why: at L267-270, which now records the exact inverse of why the exemption exists, on the field this commit just made mandatory. (3) Makefile:155 describes the components suite the same commit removed. (4) make test-unit never runs on a PR: make test has one caller, guarded by a gate that greps ^static/programs/ only, so the test check went green on this PR in 6s without executing any of the 15 new cases; make ci_pull_request runs lint, not test. One ⚠️: the pure undocumented: integrity throws sit behind the network-dependent forward check, so today they're unreachable. facts MEDIUM (live listing author-reported, not independently reachable); code correctness MEDIUM (tests read, not run). Claim set now 34 + 1 detector finding. (13587ab)

  • 2026-08-25T22:08:02Z — Re-reviewed after fix push (3 new commits, 32b2cde), on @CamSoper's #update-review. Nothing was disputed and nothing needed re-triage. All five outstanding findings → ✅ Resolved: [L103] and [L104-106] in ea95624, which fixed the cis-kubernetes inversion at the root — @danbiwer confirmed -google-cloud is the public pack and -gcp the never-productized name policy-packs-internal publishes to, so the swap makes the file agree with the registry, and the undocumented: entry was deleted rather than corrected, which is the right shape since the pack it exempted now has a page. Re-ran the arithmetic independently: 22 sections: packs = 22 files in data/policy_pack_policies/, plus 1 exemption = the 23 products, so notProducts is empty and the gate is green on this branch. slug: google-cloud unchanged, so no URL moved and no alias is owed; the policy_pack_lastmod.json key was renamed alongside the JSON, which a half-rename would have missed. [L155] and [L241-247] in 32b2cde0, the latter with a real ordering test (a malformed exemption is reported even when the forward check also fails constructs a case where both checks would throw and asserts on /"why:"/, which only the new order produces). [L92] resolved as to the wiring — make test-unit now runs from pull-request.yml's buildSite, the second option I offered and the cheaper one.

    Also audited the 143a0a00 merge against Mark compliance policy packs as Business Critical in docs and pricing #21120 on all three conflicted files, diffing the merge commit against both parents. The resolutions are right: pre-built-packs.md keeps this branch's expanded ISO/NIST/PCI rows plus the new CMMC row with Mark compliance policy packs as Business Critical in docs and pricing #21120's Edition column, policy-packs/_index.md keeps this branch's framework list (now including CMMC) with Mark compliance policy packs as Business Critical in docs and pricing #21120's edition-split sentence, and data/policy_packs.yaml carries all 8 feature: keys against 8 sections. The hand-assigned "Business Critical" on the CMMC row — the one cell the merge had to invent, since Mark compliance policy packs as Business Critical in docs and pricing #21120 never saw that row — checks out against both feature: compliance-policy-packs (available_from: business-critical) and Mark compliance policy packs as Business Critical in docs and pricing #21120's own business-critical availability string, which already named CMMC.

    One new 🚨, and it's the [L92] fix itself: the Run unit tests step is placed before make ci_pull_request, which is the only thing that runs yarn install in that job, so require("js-yaml") at fetch-policy-packs.js:17 has nothing to resolve against and the module throws at load. Not a prediction — job 97983563162 on 32b2cde0 failed at step 12 with Cannot find module 'js-yaml', skipped Build and deploy, and Install deps and build site is red at 53s, so this PR has no preview build. Suggested a one-line lazy require in main() (the only yaml call site) over the two install-shuffling alternatives, since make ensure starts with cleanrm -rf node_modules and would throw a pre-install away. Two new ⚠️: a comment paragraph stranded on staleExemptions by the hoist when it describes undocumentedProducts 15 lines below, and cis-kubernetes-gcp now being a non-product that the hand-maintained non-product list doesn't record — no check can catch that, and it's the single name most likely to be re-added by mistake. code correctness raised MEDIUM → HIGH: CI executed the new step, so the finding rests on a log rather than on reading. Claim set now 32 + 1 detector finding. (32b2cde)

  • 2026-08-25T23:20:07Z — Re-reviewed after fix push (1 new commit, 2145a5e), on @CamSoper's #update-review. Nothing was disputed. [L96] → ✅ Resolved, taking the lazy-require option: js-yaml moves from module scope to main()'s first statement, one line above its only call site. Verified the premise rather than the patch — yaml has exactly one reference in the file, the remaining top-level requires are fs/path (builtins), the four exported helpers don't touch YAML, and main() still parses the allowlist, so the nightly path is unchanged. The decisive evidence is CI's, not mine: job 97995149607 on 2145a5e runs Run unit tests at step 12 — before Build and deploy at 13, and before any yarn install — and logs tests 16 / pass 16 / fail 0; Install deps and build site is green end to end, so the preview build is back and [L92]'s coverage is live for the first time on this PR. Also independently confirmed the rewritten workflow comment, which is itself an artifact under review on a PR about comment-code drift: Makefile:25 is ensure: clean and scripts/clean.sh:5 is rm -rf node_modules, so "anything installed ahead of it is thrown away" holds and the two install-shuffling alternatives really were the worse ones. One scope note recorded in the ✅ entry and not raised as a finding: the no-node_modules property holds for the current scripts/*.test.js glob, which is one file; a future test that requires a package.json dependency at load would reproduce the same red step. Both ⚠️ are untouched by this push and stand — the staleExemptions anchor shifts to [L254-260] with the removed require line. Claim set now 36 + 1 detector finding (two new: the lazy-require comment's premise and the workflow comment's install-ordering rationale). code correctness stays HIGH, now resting on a passing run rather than a failing one. (2145a5e)

  • 2026-08-26T19:22:25Z — Re-reviewed after fix push (1 new commit, 723be65), on @CamSoper's #update-review. Both remaining ⚠️ → ✅ Resolved; every bucket is now empty. The push is comment-only in both files, which I confirmed by diffing rather than by reading the message: in scripts/fetch-policy-packs.js the only changed lines are the four-line reverse-direction paragraph moving from above staleExemptions to above const accountedFor, and in data/policy_packs.yaml the do-not-document block's header plus the new six-line cis-kubernetes-gcp entry. No executable line moved, so the 16 cases and the nightly path are untouched by construction; CI ran Run unit tests at step 14 of job 98293179231 on this head, ahead of Build and deploy at 15, and it passed.

    One concession, on the part of the fix that departed from my suggestion. @CamSoper is right that the header I proposed was wrong, and wrong in a way I should have caught: it asserted "all 13 come back source: "private"" over a block that names 14 packs. hitrust-awsnative isn't private — @jkodroff deleted it from the org, so it isn't in the listing at all. That directly contradicts the reconciliation in my own 2026-08-25 entry, which had already worked the 13 out as the 12 non-product names plus cis-kubernetes-gcp; I then wrote a suggestion that counted the block's entries instead of the listing's records. The shipped wording says which 13 and names the exception, and I re-derived it against the file as it stands: 12 names in the "must NOT be documented" list, plus cis-kubernetes-gcp = 13, none unlisted, hitrust-awsnative described but not counted.

    Two things checked beyond the diff. The relocated paragraph was also reworded from "the same failure this PR was opened to fix" to "the same failure this check exists to catch" — unprompted, and the right instinct on a change whose whole subject is comments that stop describing their code; a comment anchored to a pull request expires the moment the PR does. And the new cis-kubernetes-gcp entry agrees with the cis-kubernetes section comment at L110-116 on both the direction of the pair and the pending upstream rename, which was the specific inconsistency risk the finding named — an auditor working repo-against-org now hits the name in the list they'd actually consult. Claim set now 39 + 1 detector finding (the superseded "all 12 come back private" claim retired; three new: the reworded header, the cis-kubernetes-gcp entry, and the relocated reverse-direction paragraph). Confidence unchanged. (723be65)


  • Refresh this review — comment @claude #update-review. Say what you fixed, or which finding you dispute and why; both work in the same mention.
  • Ask for anything else — comment @claude with no hashtag (questions, one-off fixes). Leaves this review untouched.

Important

Please don't hide, resolve, or delete this comment! It breaks things!

📖 How pre-merge review works — the full lifecycle, short-circuits, and escape hatches.

@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 Aug 21, 2026
- [NIST 800-53 for AWS](/docs/reference/pre-built-policy-packs/nist/aws/)
- [PCI DSS for AWS](/docs/reference/pre-built-policy-packs/pci-dss/aws/)
- [HITRUST CSF for AWS](/docs/reference/pre-built-policy-packs/hitrust/aws/)
- [HITRUST CSF for AWS Native](/docs/reference/pre-built-policy-packs/hitrust/aws-native/) — for the AWS Cloud Control (`aws-native`) provider.

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.

This isn't actually published. It exists in our repo privately. I noticed it's appearing in references, too. Would you mind removing that in this PR?

I think some docs automation might have picked it up @jkodroff

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Good catch (sorry for the Claude-speak). I'll hit you up on Slack to figure out the source of truth on what should actually be published.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 93d5904 — removed from the table, this page, and data/policy_packs.yaml, which is what was generating the reference page. Also dropped the fetched policy data and its lastmod entry, and added an S3 redirect from /docs/reference/pre-built-policy-packs/hitrust/aws-native/ to the HITRUST AWS page, since the old URL is live today.

On how it got there, since it's relevant to the source-of-truth question: data/policy_packs.yaml is a hand-maintained allowlist, and its rule read "only document packs that are built from the policy-packs-internal repo, i.e. the ones we ship to customers" — treating those two as one condition. hitrust-awsnative satisfies the first and not the second, and nothing available to the docs side distinguishes them: it's built there, it published cleanly in the 2026-07-09 batch, and the Pulumi Cloud API returns it like any other pack, which is how the nightly fetch picked up 114 policies for it. So the automation wasn't working from a bad signal — it was working from the only signal it has, which doesn't carry the private/public bit at all.

I've reworded the rule as two independent conditions and recorded the exclusion at the bottom of that file, so an audit that diffs the org listing against the repo doesn't re-add it. But that's a comment telling a human to go ask, not a check — if there's a durable way to mark a pack private in policy-packs-internal (a private: true in its package.json, a naming convention, anything the fetch script could read), that'd be a much better fix than trusting the next person to read a comment. Happy to wire that up on the docs side once you and @jkodroff land on what the source of truth should be.

Worth a look while you're in there: the same batch had four packs fail to publish and never get retried, one of them aws-organizations-tag-policies v2.0.1 — pulumi/policy-packs-internal#211. Found by an agent, unverified by a human, so treat it accordingly.


Generated by Claude Code

claude added 2 commits August 24, 2026 16:25
Upstream restructured content/docs/administration/, which moved
setting-up-for-success.md from onboarding-guide/ to get-started/. Git
followed the rename and carried this branch's edit with it; no conflicts
to resolve by hand.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvCEyksviZ5M9vNaZvAsdz
@danbiwer flagged in review that the HITRUST AWS Native pack is private to
policy-packs-internal and is not published to customers, and that docs
automation had generated a public reference page for it at
/docs/reference/pre-built-policy-packs/hitrust/aws-native/.

Removes it from the pre-built packs table, the AWS integrations page, and
data/policy_packs.yaml, which is what generated the reference page. Also drops
the fetched policy data and its lastmod entry so the data set stays consistent
with the allowlist.

The reference page is live today, so removing it changes a public URL: adds an
S3 redirect to the HITRUST AWS page, the nearest real equivalent.

The allowlist rule is what let this through. It said to document packs "built
from the policy-packs-internal repo, i.e. the ones we ship to customers",
treating those as the same set. They aren't: this pack is built there, publishes
cleanly, and is returned by the API, and none of that makes it customer-facing.
Restates the rule as two independent conditions, notes that neither the repo nor
the API can distinguish them, and records the exclusion at the bottom of the file
so a future audit against the org doesn't re-add it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvCEyksviZ5M9vNaZvAsdz
@github-actions github-actions Bot added review:stale New commits since last Claude review; refresh on next ready-transition or @claude mention and removed review:no-blockers Claude review completed cleanly; outstanding is empty labels Aug 24, 2026

Copy link
Copy Markdown
Contributor Author

@claude #update-review

Two pushes since the last review:

  • acd985f — merged master to clear a conflict. Upstream restructured content/docs/administration/, moving setting-up-for-success.md from onboarding-guide/ to get-started/; git followed the rename and carried this branch's edit with it. Nothing resolved by hand. That also relocates the two pre-existing findings you flagged (the "AWS-reviewed" claim and the "warning or error" enforcement levels) — both are still there, still on untouched lines, now at content/docs/administration/get-started/setting-up-for-success.md.
  • 93d5904 — removed the HITRUST AWS Native pack after @danbiwer confirmed in review that it's private and not shipped to customers. Note this reverses your triage of the [L92-94] finding: the verifier called it contradicted, you (and I) overruled that on the grounds that data/policy_packs.yaml defines the pack and the data file exists — and the verifier's conclusion was right for a reason neither of us could see from this repo. The pack really is built, really does publish, and really is returned by the API; it just isn't public. Worth noting because the reasoning that overruled it looked sound and was still wrong.

The removal spans the table, the AWS integrations page, data/policy_packs.yaml, data/policy_pack_policies/hitrust-awsnative.json, and the lastmod ledger, plus an S3 redirect for the now-removed /docs/reference/pre-built-policy-packs/hitrust/aws-native/. The remaining nine added pack links are unchanged and still resolve against the allowlist.

Not disputing anything else. The out-of-scope note about content/docs/iac/comparisons/*.md and content/what-is/what-is-policy-as-code.md is recorded in the PR body as a follow-up.


Generated by Claude Code

@github-actions github-actions Bot added review:in-progress Claude review is currently running and removed review:stale New commits since last Claude review; refresh on next ready-transition or @claude mention labels Aug 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Review updated on @CamSoper's request.

@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 Aug 24, 2026
Comment thread data/policy_packs.yaml Outdated
# guide, not a policy listing. Stays hand-maintained.
# cis-kubernetes-google-cloud A stale earlier publish, superseded by
# cis-kubernetes-gcp. See the cis-kubernetes section.
# hitrust-awsnative Built here and resolvable through the API, but NOT a

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It's resolvable through the API b/c someone published it specifically to the pulumi org. We either need an allowlist on the docs end, or a "clean" org for reading things from API endpoints.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Built the allowlist option, in cd00d9a2, using the source field you and @danbiwer landed on in Slack.

data/policy_packs.yaml was already an allowlist — it's how we document 22 of the packs that repo carries rather than all of them. What it didn't have was any check on its own entries, so a human adding a wrong row was enough. fetch-policy-packs.js now requires every entry to come back from /api/registry/policypacks?orgLogin=<org> with source: "pulumi", and fails naming the offenders and their actual source.

Verified against the two records Dan pasted, including both failure modes from this incident — a source: "private" pack, and a pack missing from the listing entirely, which is hitrust-awsnative's state now that you've deleted it.

Two things from your thread are captured as comments in the code, because both are easy to get wrong later:

  • Not publisher. It's "pulumi" for everything in our org, so filtering on it admits the private packs too. It's the more obvious-looking field, so there's a comment saying don't switch to it.
  • Your spoofing question is what made a hard failure defensible. Dan's answer — source is admin-set, not set by whoever publishes — is why the check throws rather than warns. If a passing engineer could set it, gating the build on it would be theater. That reasoning is in the comment too, so it survives.

The check fails closed: an unreadable or changed response stops the job rather than publishing unverified pages.

I couldn't exercise the live call — both endpoints need a token this environment doesn't have — so the parsing is written against Dan's records and unit-tested, and the envelope key is accepted loosely since only the record shape was confirmed. First real run will settle it, and it's on the nightly, so that's tonight.

Which brings up something time-sensitive: deleting the pack from the org means master is now broken for that job. fetch-policy-packs.js throws when an allowlisted pack isn't in the org, and master still carries the entry — so the 07:30 UTC run fails and pings #docs-ops. This PR is the fix.

One more, tangential: Dan's aws-organizations-tag-policies record reads version: 1.0.0, while the repo is at 2.0.1 and there's a GitHub release for 2.0.0. If that was a live listing rather than an illustrative paste, the published version is lagging further than pulumi/policy-packs-internal#211 estimates and I should correct that issue. If it was just a paste, ignore me.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

All you have to do is look for source:pulumi. Don't need a static allowlist.

Agreed on the gate, and e7794493 makes that true in both directions. One wrinkle on deleting the file outright, though.

data/policy_packs.yaml isn't only deciding which packs — it's also carrying everything the page generator needs and the API doesn't have: URL slug, page title, H1, meta description, menu parent, weight, and the framework grouping that puts cis-aws / cis-azure / cis-google-cloud under one "CIS 8.1" section. Worth noting displayName is "" on both records Dan pasted, so we can't even derive titles from the listing.

So the file stays as the editorial layer and stops being the gate. source: pulumi now decides membership, checked both ways:

  • a documented pack that isn't a product → fail (this is what shipped in cd00d9a2)
  • a product nobody documented → fail (new in e7794493)

The second one is the same bug this PR was opened for, just pointing the other way: we shipped ISO 27001 for three clouds and the docs said AWS. Under the old design a new product pack could ship and docs would stay silent indefinitely. Now the nightly fails until someone either adds an entry or records why not.

"Why not" is now a machine-readable undocumented: list rather than a comment, holding the two genuine omissions — aws-organizations-tag-policies (hand-written prose page, not a policy listing) and cis-kubernetes-google-cloud (the stale publish superseded by cis-kubernetes-gcp). If either drops out of that list the build fails, so they're enforced rather than remembered.

On your other message — yes, publisher: pulumi was the trap. It's "pulumi" for every pack in the org including the private ones, so it would have admitted exactly what we were trying to exclude. The check uses source only, with a comment saying don't switch it, since publisher is the more natural-looking field.

Last night's run failed exactly as expected: pack "hitrust-awsnative" is in data/policy_packs.yaml but is not published to the "pulumi" org (run). master still carries the entry you deleted from the org, so that repeats nightly at 07:30 UTC until this merges.


Generated by Claude Code

@jkodroff jkodroff left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approving, since this fixes the most important mistake, but it's not a full solution.

@CamSoper

Copy link
Copy Markdown
Contributor Author

@jkodroff I'm rolling in the allowlist idea

Turns the rule in data/policy_packs.yaml from prose into a check. `/api/orgs/<org>/
policypacks`, which this script already reads, returns everything the org holds --
products and privately published packs alike, indistinguishably. That is how the
private hitrust-awsnative pack got a public reference page (#21055).

The registry listing carries the distinction in `source`: "pulumi" for a published
product, "private" otherwise. Every allowlisted pack must now come back as a product
or the job fails naming the offenders and their actual source. Verified against real
records from @danbiwer, including the two failure modes behind this incident: a
privately published pack, and a pack absent from the listing entirely.

Deliberately not `publisher`: that is "pulumi" for everything in our org, so it would
admit private packs too. `source` is set by an admin command rather than by whoever
publishes, so it cannot be set by an engineer testing something -- which is what makes
it safe to fail the build on.

Fails closed. An unreadable or changed response stops the job rather than falling back
to publishing unverified pages.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvCEyksviZ5M9vNaZvAsdz
@github-actions github-actions Bot added review:outstanding-issues Claude review completed; outstanding has author-actionable findings and removed review:in-progress Claude review is currently running labels Aug 25, 2026
The step I added ran before anything was installed, and this module required js-yaml at
load, so the suite died on MODULE_NOT_FOUND before a single case ran.

Installing first is the wrong fix: `make ensure` begins with `clean`, so anything
installed ahead of `make ci_pull_request` is thrown away. Moving the tests after the
build would report a failure only once the site had already deployed.

js-yaml is used in exactly one place -- reading the allowlist in main() -- and none of
the exported helpers need it, so requiring it there keeps the module importable with no
node_modules. Verified by hiding node_modules/js-yaml and running the suite the way CI
does: 16/16. main() still parses the allowlist.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvCEyksviZ5M9vNaZvAsdz
@github-actions github-actions Bot added review:stale New commits since last Claude review; refresh on next ready-transition or @claude mention and removed review:outstanding-issues Claude review completed; outstanding has author-actionable findings labels Aug 25, 2026
@CamSoper

Copy link
Copy Markdown
Contributor Author

@claude #update-review

One commit since the last refresh, 2145a5e, fixing the CI wiring I added for L92.

The step ran before anything was installed and this module required js-yaml at load, so the suite died on MODULE_NOT_FOUND before executing a case — the build failed, and the coverage was still effectively absent.

Installing first would have been wasted work (make ensure begins with clean, so anything installed ahead of make ci_pull_request is discarded), and moving the tests after the build would surface a failure only once the site had deployed. js-yaml is used in exactly one place, reading the allowlist inside main(), and none of the exported helpers touch it — so it's required there, and the module is now importable with no node_modules. That's what the workflow comment already claimed; it just wasn't true yet.

Verified the way CI does it: hid node_modules/js-yaml, ran the suite, 16/16. main() still parses the allowlist. Build is green and the job log shows all 16 cases executing on this PR, which was the point of the finding.

No other findings affected; the worklist was clean before this push and the change touches only the require site and a workflow comment.


Generated by Claude Code

@github-actions github-actions Bot added review:in-progress Claude review is currently running and removed review:stale New commits since last Claude review; refresh on next ready-transition or @claude mention labels Aug 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Review updated on @CamSoper's request.

@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 Aug 25, 2026
…es-gcp

Two comment-only fixes, both about the file staying honest about itself.

Hoisting the integrity checks last round moved their code but carried the
reverse-direction preamble with it, leaving a paragraph about `undocumentedProducts`
sitting above `staleExemptions` -- which is the opposite check. Moved it back to the code
it describes. Comment-code drift is the failure this whole change exists to prevent, so
introducing some while fixing it is worth undoing.

`cis-kubernetes-gcp` had nowhere to live after ea95624 stopped documenting it: not in
`sections:`, not in `undocumented:`, and not in the do-not-document list whose stated job
is stopping an audit from re-adding a name. It is the name most likely to be re-added by
mistake, being the plausible-looking half of the pair and the exact mistake just fixed,
and no check can catch it -- a private pack nothing references is invisible to both
directions of the gate. Now recorded with the reason.

The header claimed all listed packs come back source: "private", which glossed
hitrust-awsnative: it is not private, it is gone from the org. Reworded to say which 13
are private and to name the exception. Verified against the live listing: 13 private, all
13 accounted for, none unlisted.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BvCEyksviZ5M9vNaZvAsdz
@github-actions github-actions Bot added review:stale New commits since last Claude review; refresh on next ready-transition or @claude mention and removed review:no-blockers Claude review completed cleanly; outstanding is empty labels Aug 26, 2026
@CamSoper

Copy link
Copy Markdown
Contributor Author

@claude #update-review

Both fixed in 723be65, comment-only.

  • L254-260 — you're right, and it's the ironic one: hoisting the integrity checks carried the reverse-direction paragraph along with the code, leaving text about undocumentedProducts sitting above staleExemptions. Moved back to the check it describes, blank line restored.

  • L283-301 — recorded cis-kubernetes-gcp with the reason. Your argument for why is the right one: it's the plausible-looking half of the pair and the exact mistake this change just fixed, and no check can catch it, since a private pack nothing references is invisible to both directions of the gate.

    On the count — the header was wrong in a way the suggestion didn't quite capture. It claimed every name in that block comes back source: "private", but hitrust-awsnative isn't private, it's gone from the org entirely. Reworded to say which 13 are private (cis-kubernetes-gcp plus the 12 named below) and to call out the exception. Verified against the live listing: 13 private, all 13 accounted for, none unlisted.

Re-ran the real fetch against the live API afterward: exit 0, and data/policy_pack_policies/ and the lastmod ledger are still byte-identical to what the API returns. 16/16 tests, lint and prettier clean.

Worklist is clean. Merging once this comes back.


Generated by Claude Code

@github-actions github-actions Bot added review:in-progress Claude review is currently running and removed review:stale New commits since last Claude review; refresh on next ready-transition or @claude mention labels Aug 26, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🤖 Review updated on @CamSoper's request.

@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 Aug 26, 2026
@github-actions github-actions Bot added review:stale New commits since last Claude review; refresh on next ready-transition or @claude mention and removed review:no-blockers Claude review completed cleanly; outstanding is empty labels Aug 26, 2026
@CamSoper
CamSoper merged commit df55c20 into master Aug 26, 2026
10 checks passed
@CamSoper
CamSoper deleted the claude/policy-funnel-draft-context-uc1jpa branch August 26, 2026 19:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain:docs PR touches technical docs review:stale New commits since last Claude review; refresh on next ready-transition or @claude mention

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants