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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: PRD policy — read this first
url: https://github.com/databrainhq/.github/blob/main/docs/PRD-POLICY.md
about: Why every feature and hotfix needs a PRD, and which template to pick.
66 changes: 66 additions & 0 deletions .github/ISSUE_TEMPLATE/prd-lite.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: "PRD (lite)"
description: "Bug fix, hotfix RCA, or a small self-contained feature. One page."
title: "PRD: "
labels: ["PRD", "prd:lite", "prd:draft"]
body:
- type: markdown
attributes:
value: |
One-page PRD. Use this for fixes, hotfix RCAs, and small self-contained
features inside one repo.

If the change crosses a repo boundary, changes a contract, or needs a
migration, use **PRD (full)** instead.

A PR cannot merge until this issue carries the `prd-approved` label.

- type: input
id: owner
attributes:
label: Owner
validations: { required: true }

- type: dropdown
id: repo
attributes:
label: Repo
options: [frontend-mono, backend, plugin-in-app, mcp-server, hasura, docs, website, other]
validations: { required: true }

- type: dropdown
id: kind
attributes:
label: Kind
options: ["feature (small)", "bug fix", "hotfix / RCA", "chore with user-visible effect"]
validations: { required: true }

- type: textarea
id: shipping
attributes:
label: What are we shipping?
description: >
For a hotfix, this is the RCA. What broke, why it broke, and what stops it
happening again.
validations: { required: true }

- type: textarea
id: success
attributes:
label: How do we know it worked?
description: What you check after the merge.
validations: { required: true }

- type: textarea
id: notdoing
attributes:
label: What are we explicitly NOT doing?
description: Keeps the fix from growing in review. One or two lines is fine.
validations: { required: true }

- type: checkboxes
id: ack
attributes:
label: Before you request review
options:
- label: "This really is small enough for a lite PRD."
required: true
112 changes: 112 additions & 0 deletions .github/ISSUE_TEMPLATE/prd.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
name: "PRD (full)"
description: "Feature or change that crosses a repo boundary, changes a contract, or needs a migration."
title: "PRD: "
labels: ["PRD", "prd:draft"]
body:
- type: markdown
attributes:
value: |
Full PRD. Use this when the change crosses a repo boundary, changes an API or
storage contract, needs a migration, or a reviewer could reasonably disagree
about the approach.

Smaller than that? Close this and open a **PRD (lite)** instead.

A PR cannot merge until this issue carries the `prd-approved` label.

- type: input
id: owner
attributes:
label: Owner
description: One name. The person who answers review comments.
validations: { required: true }

- type: dropdown
id: repos
attributes:
label: Repos affected
multiple: true
options: [frontend-mono, backend, plugin-in-app, mcp-server, hasura, docs, website, other]
validations: { required: true }

- type: textarea
id: links
attributes:
label: Links
description: Specs, ADRs, superseded docs, related PRDs. One per line.
placeholder: |
Schema: specs/semantic-layer-v2.md
Decision: docs/adr/0001-....md
Supersedes: #1234

- type: textarea
id: shipping
attributes:
label: What are we shipping?
description: The thing itself, not the motivation. One paragraph.
validations: { required: true }

- type: textarea
id: success
attributes:
label: How do we know it worked?
description: Outcomes you can check after the merge. Not a feeling.
validations: { required: true }

- type: textarea
id: hard
attributes:
label: What's the hard part?
description: The real constraint. If there is no hard part, use PRD (lite) instead.
validations: { required: true }

- type: textarea
id: notdoing
attributes:
label: What are we explicitly NOT doing?
description: >
The most important section. List what is cut. Mark each as cut or deferred and
mean it. Anything revived later needs its own PRD.
validations: { required: true }

- type: textarea
id: scope
attributes:
label: Scope
description: What ships together, and what is out.
value: |
| | In | Out |
| ------------------- | --- | --- |
| Ships together | | |
| Nice later, no date | | |
validations: { required: true }

- type: input
id: contract
attributes:
label: One contract
description: The single sentence a reviewer should be able to repeat back.
validations: { required: true }

- type: textarea
id: rollout
attributes:
label: Rollout
description: Flags, migrations, merge order across repos, and the back-out.
validations: { required: true }

- type: textarea
id: risks
attributes:
label: Risks
description: What breaks if we are wrong, and who notices first.

- type: checkboxes
id: ack
attributes:
label: Before you request review
options:
- label: "The NOT doing section is filled in, not left as a placeholder."
required: true
- label: "I have read docs/PRD-POLICY.md."
required: true
23 changes: 23 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!--
This PR cannot merge until the PRD it references carries the `prd-approved` label.
Policy: https://github.com/databrainhq/.github/blob/main/docs/PRD-POLICY.md
-->

**PRD:** #

## What changed

<!-- One paragraph. What a reviewer will see in the diff. -->

## Deviations from the PRD

<!--
The section reviewers should read hardest. If the build differs from the approved
PRD, say so here and say why. "None" is a valid answer, but only if it is true.
-->

None.

## How this was verified

<!-- Tests, manual steps, screenshots. "It compiles" is not verification. -->
181 changes: 181 additions & 0 deletions .github/workflows/prd-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# Reusable PRD gate. Called by each repo that enforces the policy.
#
# A PR may merge only if it references an issue that carries BOTH the `PRD`
# and `prd-approved` labels — or if the PR itself carries `prd-exempt`.
#
# Caller usage (see setup/caller-workflow.yml):
# jobs:
# prd:
# uses: databrainhq/.github/.github/workflows/prd-gate.yml@main
# secrets:
# cross_repo_token: ${{ secrets.PRD_GATE_TOKEN }}

name: PRD gate

on:
workflow_call:
inputs:
exempt_label:
description: Label on the PR that skips the gate.
type: string
default: prd-exempt
approved_label:
description: Label the PRD issue must carry.
type: string
default: prd-approved
secrets:
cross_repo_token:
description: >
Token that can read issues in other databrainhq repos. Optional — without
it, only same-repo PRD references can be verified.
required: false

permissions:
contents: read
pull-requests: read
issues: read

jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v7

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 security Mutable workflow action reference

actions/github-script@v7 resolves through a mutable tag while this step receives repository permissions and the cross-repository issue token. Pinning the action to a reviewed full commit SHA prevents an upstream tag change from executing different code with those credentials.

How this was verified: The executable action uses the mutable v7 tag and the same step receives CROSS_REPO_TOKEN through its environment.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/prd-gate.yml
Line: 42

Comment:
**Mutable workflow action reference**

`actions/github-script@v7` resolves through a mutable tag while this step receives repository permissions and the cross-repository issue token. Pinning the action to a reviewed full commit SHA prevents an upstream tag change from executing different code with those credentials.

**How this was verified:** The executable action uses the mutable `v7` tag and the same step receives `CROSS_REPO_TOKEN` through its environment.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Cursor Fix in Codex

env:
EXEMPT_LABEL: ${{ inputs.exempt_label }}
APPROVED_LABEL: ${{ inputs.approved_label }}
CROSS_REPO_TOKEN: ${{ secrets.cross_repo_token }}
with:
script: |
const pr = context.payload.pull_request;
if (!pr) { core.setFailed('PRD gate must run on pull_request events.'); return; }

const EXEMPT = process.env.EXEMPT_LABEL;
const APPROVED = process.env.APPROVED_LABEL;
const POLICY = 'https://github.com/databrainhq/.github/blob/main/docs/PRD-POLICY.md';

// --- escape hatch ------------------------------------------------
const prLabels = (pr.labels || []).map(l => l.name.toLowerCase());
if (prLabels.includes(EXEMPT.toLowerCase())) {
core.notice(`PR carries "${EXEMPT}" — gate skipped. A human with write access applied it.`);
return;
}

// --- collect candidate PRD references ----------------------------
// Sources: PR body, PR title, and the branch name.
const haystack = [pr.body || '', pr.title || '', pr.head?.ref || ''].join('\n');
const owner = context.repo.owner;
const refs = new Map(); // key -> {owner, repo, number}

const add = (o, r, n) => {
const key = `${o}/${r}#${n}`;
if (!refs.has(key)) refs.set(key, { owner: o, repo: r, number: Number(n) });
};

// https://github.com/OWNER/REPO/issues/123
for (const m of haystack.matchAll(/https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+)\/issues\/(\d+)/gi)) {
add(m[1], m[2], m[3]);
}
// OWNER/REPO#123
for (const m of haystack.matchAll(/\b([\w.-]+)\/([\w.-]+)#(\d+)\b/g)) {
add(m[1], m[2], m[3]);
}
// bare #123 -> this repo
for (const m of haystack.matchAll(/(?:^|[^\w/])#(\d+)\b/g)) {
add(owner, context.repo.repo, m[1]);
}

if (refs.size === 0) {
core.setFailed(
'No PRD linked.\n\n' +
'Add a line to the PR description that links the PRD issue, for example:\n' +
' PRD: #1234\n' +
' PRD: databrainhq/backend#3379\n\n' +
`The issue must carry the "PRD" and "${APPROVED}" labels.\n` +
`Policy: ${POLICY}`
);
return;
}

// --- resolve each reference --------------------------------------
const crossToken = process.env.CROSS_REPO_TOKEN;

// Cross-repo reads go over plain fetch so we never depend on a second
// Octokit instance being require-able inside github-script.
async function readIssue(ref, sameRepo) {
if (sameRepo) {
const r = await github.rest.issues.get({
owner: ref.owner, repo: ref.repo, issue_number: ref.number,
});
return r.data;
}
if (!crossToken) {
const e = new Error('no PRD_GATE_TOKEN secret set');
e.noToken = true;
throw e;
}
const res = await fetch(
`https://api.github.com/repos/${ref.owner}/${ref.repo}/issues/${ref.number}`,
{ headers: {
Authorization: `Bearer ${crossToken}`,
Accept: 'application/vnd.github+json',
'User-Agent': 'databrainhq-prd-gate',
} },
);
Comment on lines +116 to +123

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 security Token-backed issue metadata oracle

If the shared token can read an issue that is hidden from readers of the caller repository, a PR author can submit that unrestricted owner/repository reference and the gate publishes the issue title and URL in the caller's workflow output. Restrict lookups to repositories with a compatible visibility boundary or avoid emitting metadata obtained with the shared credential.

How this was verified: PR-controlled repository coordinates flow without an allowlist into the token-authenticated fetch, and the returned metadata is written to notices, summaries, and failure output.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/prd-gate.yml
Line: 116-123

Comment:
**Token-backed issue metadata oracle**

If the shared token can read an issue that is hidden from readers of the caller repository, a PR author can submit that unrestricted owner/repository reference and the gate publishes the issue title and URL in the caller's workflow output. Restrict lookups to repositories with a compatible visibility boundary or avoid emitting metadata obtained with the shared credential.

**How this was verified:** PR-controlled repository coordinates flow without an allowlist into the token-authenticated fetch, and the returned metadata is written to notices, summaries, and failure output.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Cursor Fix in Codex

if (!res.ok) {
const e = new Error(`HTTP ${res.status}`);
e.status = res.status;
throw e;
}
return res.json();
}

const report = [];
let approvedRef = null;

for (const ref of refs.values()) {
const sameRepo = ref.owner === owner && ref.repo === context.repo.repo;
let issue;
try {
issue = await readIssue(ref, sameRepo);
} catch (e) {
report.push(
`- ${ref.owner}/${ref.repo}#${ref.number} — could not read (${e.status || e.message})`
+ (e.noToken ? '. Set the PRD_GATE_TOKEN secret to allow cross-repo checks.' : '')
);
continue;
}

// A PR is not a PRD, even though the issues API returns both.
if (issue.pull_request) {
report.push(`- ${ref.owner}/${ref.repo}#${ref.number} — is a pull request, not a PRD issue`);
continue;
}

const labels = (issue.labels || [])
.map(l => (typeof l === 'string' ? l : l.name).toLowerCase());
const isPrd = labels.includes('prd');
const isApproved = labels.includes(APPROVED.toLowerCase());

if (isPrd && isApproved) { approvedRef = { ref, issue }; break; }
if (isPrd) report.push(`- ${ref.owner}/${ref.repo}#${ref.number} "${issue.title}" — PRD found, but not yet ${APPROVED}`);
else report.push(`- ${ref.owner}/${ref.repo}#${ref.number} "${issue.title}" — not labelled PRD`);
}

if (approvedRef) {
const { ref, issue } = approvedRef;
core.notice(`Approved PRD: ${ref.owner}/${ref.repo}#${ref.number} — ${issue.title}`);
await core.summary
.addHeading('PRD gate passed', 3)
.addRaw(`Approved PRD: [${ref.owner}/${ref.repo}#${ref.number}](${issue.html_url}) — ${issue.title}`)
.write();
return;
}

core.setFailed(
`No approved PRD found for this PR.\n\n${report.join('\n')}\n\n` +
`Fix one of these:\n` +
` 1. Link the right PRD issue in the PR description ("PRD: #1234").\n` +
` 2. Get the PRD approved — a reviewer adds the "${APPROVED}" label.\n` +
` 3. If this genuinely needs no PRD, a maintainer adds "${EXEMPT}" to this PR.\n\n` +
`Policy: ${POLICY}`
);
Loading