Files
opencode-mobile/.github/workflows/product-intelligence.yml
Den 5c14ce0a5d feat: add daily product intelligence and versioned site assets (#64)
Adds privacy-safe aggregate product intelligence, reviewed/versioned website assets, and a dispatch-only rollout until the dedicated Sentry token is verified. Independent review blockers were fixed in 8bc47e4; app checks, website production build, Android CI, and iOS CI are green.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-07-15 11:59:40 -07:00

127 lines
4.4 KiB
YAML

name: Daily Product Intelligence
on:
workflow_dispatch:
permissions:
actions: read
contents: read
issues: write
concurrency:
group: product-intelligence-${{ github.ref }}
cancel-in-progress: false
jobs:
report:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 24
- name: Collect aggregate product signals
id: collect
continue-on-error: true
env:
GITHUB_TOKEN: ${{ github.token }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_PRODUCT_INTELLIGENCE_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
run: |
node scripts/product-intelligence.mjs \
--report "$RUNNER_TEMP/product-intelligence.md" \
--json "$RUNNER_TEMP/product-intelligence.json"
- name: Create material-signal issue
if: always()
uses: actions/github-script@v8
env:
REPORT_PATH: ${{ runner.temp }}/product-intelligence.json
with:
script: |
const fs = require("fs")
if (!fs.existsSync(process.env.REPORT_PATH)) {
core.setFailed("Product intelligence collector produced no report.")
return
}
const report = JSON.parse(fs.readFileSync(process.env.REPORT_PATH, "utf8"))
const allowedSignals = new Set([
"new-sentry-issue",
"repeated-workflow-failure",
])
const validDate = typeof report.date === "string" && /^\d{4}-\d{2}-\d{2}$/.test(report.date)
const validSignals = Array.isArray(report.signals) &&
report.signals.length > 0 &&
report.signals.every((signal) => allowedSignals.has(signal))
if (!report.material) {
core.info("No material signal. No GitHub issue created.")
return
}
if (!validDate || !validSignals) {
core.setFailed("Collector report failed public issue allowlist validation.")
return
}
const issues = await github.paginate(github.rest.issues.listForRepo, {
owner: context.repo.owner,
repo: context.repo.repo,
state: "open",
per_page: 100,
})
const title = "Product intelligence: material signal"
const marker = `<!-- product-intelligence:${report.date}:${[...report.signals].sort().join(",")} -->`
const body = [
marker,
"",
"## Material aggregate signal",
"",
`The daily product-intelligence run detected: ${[...report.signals].sort().join(", ")}.`,
"",
"This issue intentionally contains no raw diagnostic, review, request, or user-generated content. Review the sanitized Actions artifact and reproduce the behavior in the affected user channel before implementing a fix.",
].join("\n")
const existing = issues.find((issue) => issue.title === title)
if (existing) {
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: existing.number,
title,
body,
})
core.info(`Updated #${existing.number}.`)
return
}
const { data: issue } = await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
body,
labels: ["P1"],
})
core.info(`Created #${issue.number}.`)
- name: Upload sanitized report
if: always()
uses: actions/upload-artifact@v7
with:
name: product-intelligence-${{ github.run_id }}
path: |
${{ runner.temp }}/product-intelligence.md
${{ runner.temp }}/product-intelligence.json
if-no-files-found: error
- name: Fail when a required source is unavailable
if: steps.collect.outcome == 'failure'
run: |
echo "::error::A required product-intelligence source was unavailable. See the report summary for the exact source and error."
exit 1