mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 22:14:25 +01:00
* fix(land-and-deploy): never merge over red, pending or missing CI (#2995) /land-and-deploy gated only on required checks, so a repo with none (every GitHub Free private repo, any repo without branch protection) skipped the CI wait and could merge over red CI. The verdict now comes from bin/gstack-ci-gate, a tested helper bound to the head SHA: all checks by default, cancel/unknown buckets fail, NO_CHECKS needs explicit per-head approval, and both merge commands (including --auto) run only after a PASS for the same SHA they pin with --match-head-commit. Co-authored-by: harshitgavita-07 <harshit.gavita@gmail.com> * fix(slug): give nested-group projects their own state bucket and verify checkpoint identity (#3003, #2901) gstack-slug kept only the last two remote path segments, so customer-a/product/repo and customer-b/product/repo shared one bucket and /context-restore could present another project's checkpoint. - One canonical-remote rule (bash + TS, golden vectors) used by all six resolvers: lowercased host, no userinfo/port/.git, Azure DevOps and Bitbucket Server normalized. 3+ segment remotes get <last-two>-<16 hex>; two-segment remotes (all of GitHub) are unchanged. - Versioned sticky cache (v2:) keeps cache hits git-free. - context-save stamps the credential-free remote and root; context-restore only treats this project's checkpoints as latest, reports foreign/unverified ones first, and treats a different root with the same remote (Conductor handoff) as information. - gstack-slug --adopt-legacy copies only files the user confirms. - question-preference-hook reads the same versioned cache, so never-ask preferences fire; unknown identity asks instead of auto-deciding. * fix(gbrain): brain context loads, managed sync works, memory sync stops duplicating (#2883, #2985, #2922) - brain-context-load called 'gbrain list_pages' (an MCP tool name), so every skill's brain context was 0 bytes. It now uses 'gbrain list' with gbrain's real filter flags, and prints one line when queries fail. - The code-stage sync always passes --no-pull (managed gbrain >= 0.51 refuses without it); a gbrain that rejects the flag fails with an upgrade message and is never retried without it. stderr is streamed with a bounded tail and quoted in failure summaries. - /sync-gbrain gains --sources (and GSTACK_MEMORY_INGEST_SOURCES); by default it skips types already synced through a federated curated source, so curated pages stop being re-duplicated. Co-authored-by: Vitor Cepeda Lopes <vitor@vitorcepedalopes.com> Co-authored-by: harshitgavita-07 <harshit.gavita@gmail.com> * fix(codex,cso): clean Codex reviews stop failing; semgrep and trivy SARIF results import (#2769, #3011) - Codex review gate is PASS / FAIL / UNVERIFIED. Untagged output (what a clean 'codex review --base' prints) is UNVERIFIED, not FAIL, and never a false PASS; P2/P3-only output passes. - import-sarif resolves semgrep's undeclared %SRCROOT% and trivy's ROOTPATH (including git-URL targets) through explicit, captured mappings; declared bases must sit inside the source root or the run's checkout, URIs are percent-decoded, and rejected results are counted with rule, base and path instead of silently dropped. * fix: version strings are not IPs, failed update checks are not 'up to date', private security reporting (#2856, #2786, #2984) - The pre-push redact hook exempts a root VERSION file that is only a version string and the version token in CHANGELOG release headings; the engine allowlist is untouched. - gstack-update-check caches an unreadable remote as CHECK_FAILED with a short TTL instead of UP_TO_DATE; /gstack-upgrade never reports 'latest' after a failed check. - SECURITY.md points reporters at GitHub private vulnerability reporting. - pr-title-sync (pull_request_target, write token) pins actions/checkout to a reviewed SHA. * fix(autoplan): publication guard accepts real Claude Code journals (#3007, #3009, #2968, #2977, #2986, #3006) The guard required a session journal to start with a user message, but Claude Code starts it with SessionStart hook attachments (and a forked compacted session starts with a compact_boundary), so every phase was denied for anyone with a SessionStart hook. Windows path spellings (C:/, /c/, drive case) were rejected too. - Reader accepts the captured startup, compact, clear and fork shapes (claude 2.1.284 fixture), computes root uniqueness over every candidate, and returns typed reason codes instead of one string. - Hard denials (competing root, foreign cwd, sidechain, agent, cycle, malformed, identity) say what happened and how to run the reviews by hand; unflushed journals still retry. - A well-formed, stable journal whose root shape is unrecognized now warns ('phase publication was NOT verified') and continues, without overriding Claude Code's own permission check; no bypass switch. - Windows paths fold before validation, so '..' is still rejected. - Periodic drift canary runs the reader on fresh journals from the pinned claude. Co-authored-by: Charles M Berg <77517285+CharlesBerg85@users.noreply.github.com> Co-authored-by: laurenbdaniels-coder <264343335+laurenbdaniels-coder@users.noreply.github.com> Co-authored-by: Jordan Mayer <176445995+maydaycyber@users.noreply.github.com> Co-authored-by: elmesery-dev <elmesery.dev@gmail.com> * v1.91.13.0: severe bug fix wave release notes, stale gbrain verbs in docs * test: seed the versioned slug cache in the memory-injection deny case (#2901) The preference hook now resolves the project bucket from gstack-slug's v2 slug-cache entry instead of basename(cwd); this fixture still wrote preferences under the basename bucket without the entry the skill preamble always writes first. * fix: full-suite follow-ups for the fix wave - gstack-slug's --help heredoc is over 512B, so it gets the BASH_COMPAT=50 pipe-deadlock guard like the other bins. - Refresh the codex/factory ship goldens for the plan-gates slug change. - The periodic lane gains the autoplan journal drift canary (107 paid files); raise the eval-slices cap to 25 so every slice still starts at once until its recorded duration lets the planner pack it. * fix(plan-eng-review): clearer Step 0 sequencing, write routes and section outputs The workflow judge scored plan-eng-review's instructions at actionability 3.67 (bar 4) on text unchanged from main. Resolve the ambiguities it named together, without changing any gate or output: Step 0 names the Read's order and keeps one STOP; the pending-call, read-only, forbidden-write and failed-save rules become one Write routes block; header/label limits and grid checks are concrete; Performance gets a checklist; each section has a stated output shape; Lake Score is defined. Same total length (parity cap). --------- Co-authored-by: harshitgavita-07 <harshit.gavita@gmail.com> Co-authored-by: Vitor Cepeda Lopes <vitor@vitorcepedalopes.com> Co-authored-by: Charles M Berg <77517285+CharlesBerg85@users.noreply.github.com> Co-authored-by: laurenbdaniels-coder <264343335+laurenbdaniels-coder@users.noreply.github.com> Co-authored-by: Jordan Mayer <176445995+maydaycyber@users.noreply.github.com> Co-authored-by: elmesery-dev <elmesery.dev@gmail.com>
100 lines
4.3 KiB
YAML
100 lines
4.3 KiB
YAML
name: PR Title Sync
|
|
|
|
# WHY pull_request_target (not pull_request): the default GITHUB_TOKEN is
|
|
# READ-ONLY on fork PRs under `pull_request`, so the title-sync backstop could
|
|
# never `gh pr edit` a fork/agent PR. `pull_request_target` runs in the base-repo
|
|
# context with a write token, which fixes fork coverage.
|
|
#
|
|
# WHY this is SAFE (pull_request_target is the most dangerous trigger):
|
|
# - We check out the BASE repo (no `ref:`), so the only code we execute is
|
|
# trusted base-repo infra (bin/gstack-pr-title-rewrite.sh). We NEVER check
|
|
# out or run PR-head/fork code.
|
|
# - Every attacker-controlled PR field (title, head repo, head sha) arrives via
|
|
# `env:` and is referenced as a shell-quoted "$VAR". We NEVER inline a
|
|
# `${{ github.event.pull_request.* }}` expression inside the run: script
|
|
# (that would execute a crafted title as shell).
|
|
# - The PR-head VERSION is read as DATA via the API (raw media type), from the
|
|
# head repo at the head sha — never by checking out the head.
|
|
# test/pr-title-sync-workflow-safety.test.ts is the static tripwire for all of
|
|
# the above and fails CI if any of it regresses.
|
|
|
|
on:
|
|
pull_request_target:
|
|
types: [opened, synchronize, edited]
|
|
paths:
|
|
- 'VERSION'
|
|
|
|
concurrency:
|
|
group: pr-title-sync-${{ github.event.pull_request.number }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
sync:
|
|
name: Sync PR title to VERSION
|
|
runs-on: ubicloud-standard-2
|
|
timeout-minutes: 5
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
if: github.actor != 'github-actions[bot]'
|
|
steps:
|
|
# Base repo only — trusted infra (the rewrite helper). No PR-head checkout.
|
|
- name: Checkout base repo (trusted)
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
fetch-depth: 1
|
|
|
|
- name: Rewrite PR title to match VERSION
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
PR_NUM: ${{ github.event.pull_request.number }}
|
|
# Attacker-controlled on fork PRs — env-only, never inlined into run:.
|
|
OLD_TITLE: ${{ github.event.pull_request.title }}
|
|
BASE_REPO: ${{ github.repository }}
|
|
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
|
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
chmod +x ./bin/gstack-pr-title-rewrite.sh
|
|
|
|
if [ "$HEAD_REPO" = "$BASE_REPO" ]; then IS_FORK=0; else IS_FORK=1; fi
|
|
|
|
# Read the PR-head VERSION as data (raw bytes), from the head repo at
|
|
# the head sha. Guard the assignment itself: under `set -e` a bare
|
|
# `VERSION=$(...)` would abort the step before any later [ -z ] check.
|
|
if ! VERSION=$(gh api -H "Accept: application/vnd.github.raw" \
|
|
"repos/$HEAD_REPO/contents/VERSION?ref=$HEAD_SHA" 2>/dev/null | tr -d '[:space:]'); then
|
|
VERSION=""
|
|
fi
|
|
|
|
if [ -z "$VERSION" ]; then
|
|
# Same-repo read failure should never happen — fail loudly so we
|
|
# notice. A fork miss (public-contents quirk, private fork) is a
|
|
# convenience gap, not a gate — warn and skip so the check stays green.
|
|
if [ "$IS_FORK" = "0" ]; then
|
|
echo "::error::Could not read VERSION from same-repo PR head ($HEAD_SHA)."
|
|
exit 1
|
|
fi
|
|
echo "::warning::Could not read VERSION from fork $HEAD_REPO ($HEAD_SHA); skipping title sync."
|
|
exit 0
|
|
fi
|
|
|
|
# The helper rejects a malformed VERSION (exit 2). Same policy: loud for
|
|
# same-repo, soft for forks. Never echo the raw (attacker-controlled)
|
|
# title — Actions still parses ::workflow-command:: from stdout.
|
|
if ! NEW_TITLE=$(./bin/gstack-pr-title-rewrite.sh "$VERSION" "$OLD_TITLE"); then
|
|
if [ "$IS_FORK" = "0" ]; then
|
|
echo "::error::Could not compute title for VERSION '$VERSION' on PR #$PR_NUM."
|
|
exit 1
|
|
fi
|
|
echo "::warning::Could not compute title for fork PR #$PR_NUM; skipping."
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$NEW_TITLE" = "$OLD_TITLE" ]; then
|
|
echo "PR #$PR_NUM title already correct; no change."
|
|
exit 0
|
|
fi
|
|
gh pr edit "$PR_NUM" --title "$NEW_TITLE"
|
|
echo "PR #$PR_NUM title synced to VERSION."
|