From 9c0c783ab7e8ffae38ab9b52f54981497f959f75 Mon Sep 17 00:00:00 2001 From: Hephaestus Date: Sat, 11 Jul 2026 17:24:28 +0000 Subject: [PATCH 1/3] chore: upgrade bootstrap manifest to v2 --- .devcontainer/devcontainer.json | 9 +- .github/PULL_REQUEST_TEMPLATE.md | 32 ++-- .github/workflows/claude.yml | 52 ++---- .github/workflows/extended-validation.yml | 32 ++-- .github/workflows/pr-fast-ci.yml | 150 +++++++--------- AGENTS.md | 16 +- CLAUDE.md | 26 +-- CODEOWNERS | 3 +- CONTRIBUTING.md | 58 ++---- SECURITY.md | 29 +-- docs/bootstrap/claude-environment.md | 55 +----- docs/bootstrap/next-steps.md | 16 +- docs/bootstrap/onboarding.md | 44 +++-- docs/bootstrap/versioning.md | 48 +++++ project.bootstrap.yaml | 132 +++++++++++++- scripts/check-detect-secrets.sh | 204 ++++++++-------------- scripts/ci/run-extended-validation.sh | 14 +- scripts/ci/run-fast-checks.sh | 20 +-- scripts/ci/run-release-build.sh | 23 +++ scripts/ci/run-release-publish.sh | 5 + scripts/ci/run-release-verification.sh | 5 + scripts/ci/run-release-version.sh | 13 ++ scripts/claude-cloud/setup.sh | 31 +--- scripts/claude/setup-devcontainer.sh | 28 +-- 24 files changed, 508 insertions(+), 537 deletions(-) create mode 100644 docs/bootstrap/versioning.md create mode 100644 scripts/ci/run-release-build.sh create mode 100644 scripts/ci/run-release-publish.sh create mode 100644 scripts/ci/run-release-verification.sh create mode 100644 scripts/ci/run-release-version.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 115aca1..319c0ce 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -16,12 +16,5 @@ "mounts": [ "source=${localEnv:HOME}/.claude,target=/home/vscode/.claude,type=bind" ], - "postCreateCommand": "bash scripts/claude/setup-devcontainer.sh", - "customizations": { - "vscode": { - "settings": { - "terminal.integrated.defaultProfile.linux": "bash" - } - } - } + "postCreateCommand": "bash scripts/claude/setup-devcontainer.sh" } diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 49145ec..e2316fc 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,22 +1,30 @@ ## Summary -- +- -## Scope +## Governing Issue -- R milestone: -- Linked issue: +Refs # -## Test Plan +## Validation -- [ ] `bash scripts/ci/run-fast-checks.sh` +- [ ] Relevant local checks passed +- [ ] Required PR checks are expected to satisfy `CI Gate` +- [ ] Skipped checks are explained below -## Documentation +## Bootstrap Governance -- [ ] README/docs updated or not needed -- [ ] Public scope/stub status still accurate +- [ ] Changes are scoped to the linked issue +- [ ] Contributor or PR guidance changes are reflected in `CONTRIBUTING.md`, `.github/PULL_REQUEST_TEMPLATE.md`, and `docs/bootstrap/onboarding.md` when applicable +- [ ] PR author enabled auto-merge where GitHub allows it, or GitHub plan-limit evidence/unavailable reason is recorded and the fallback merge-readiness policy applies +- [ ] No real secrets, runtime auth, or machine-local env files are committed -## Fixtures -- [ ] Fixture changes are synthetic metadata only -- [ ] No raw mail bodies, secrets, runtime auth, generated databases, or caches + +## Merge Automation + +- [ ] PR author enabled auto-merge with `gh pr merge --auto --squash`, or the reason it is unavailable/unsafe is noted below + +## Notes + +- diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 3695a0c..e249977 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -4,9 +4,9 @@ on: workflow_dispatch: inputs: prompt: - description: 'Task for Claude to run in this repository' - required: true - default: 'Review the current branch changes for bugs, CI regressions, and missing tests.' + description: Optional manual task prompt + required: false + type: string issue_comment: types: [created] pull_request_review_comment: @@ -20,55 +20,27 @@ concurrency: permissions: contents: read + pull-requests: write + issues: write jobs: claude: if: | github.event_name == 'workflow_dispatch' || - (github.actor == 'jmcte' && github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.actor == 'jmcte' && github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.actor == 'jmcte' && github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) + (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) runs-on: ubuntu-latest timeout-minutes: 30 - permissions: - contents: write - pull-requests: write - issues: write - id-token: write - actions: read steps: - - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - fetch-depth: 1 - persist-credentials: false - - - name: Require Claude auth - env: - ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} - run: | - if [[ -z "${ANTHROPIC_API_KEY}" ]]; then - echo "Missing repository secret ANTHROPIC_API_KEY. Run /install-github-app in Claude Code or add the secret before using this workflow." >&2 - exit 1 - fi - + - uses: actions/checkout@v4 - name: Run Claude Code - uses: anthropics/claude-code-action@e90deca47693f9457b72f2b53c17d7c445a87342 # v1.0.171 + uses: anthropics/claude-code-action@v1 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} - track_progress: true - use_sticky_comment: true - additional_permissions: "actions: read" prompt: | REPO: ${{ github.repository }} DEFAULT BRANCH: main - - Use CLAUDE.md and docs/bootstrap/onboarding.md as repo policy context. - Keep CI Gate as the single required PR status check. - Preserve the split fast and extended validation model. - Shell-safe jobs may use `[self-hosted, synology, shell-only, private]`. - Docker, service-container, browser, and `container:` jobs stay on GitHub-hosted runners. - Prefer the smallest safe change and add tests for behavior changes. - + REQUIRED CHECKS: CI Gate + Use CLAUDE.md, AGENTS.md, and docs/bootstrap/onboarding.md as policy context. MANUAL TASK: ${{ github.event.inputs.prompt }} - If this is not a manual run, ignore the MANUAL TASK line and respond to the current `@claude` request instead. diff --git a/.github/workflows/extended-validation.yml b/.github/workflows/extended-validation.yml index 38c5a9a..3d9774c 100644 --- a/.github/workflows/extended-validation.yml +++ b/.github/workflows/extended-validation.yml @@ -25,13 +25,13 @@ defaults: jobs: changes: name: Detect Extended Validation Scope - runs-on: ['self-hosted', 'synology', 'shell-only', 'private'] + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] outputs: app: ${{ steps.preset.outputs.app || steps.filter.outputs.app || 'false' }} ci: ${{ steps.preset.outputs.ci || steps.filter.outputs.ci || 'false' }} extended: ${{ steps.preset.outputs.extended || steps.filter.outputs.extended || 'false' }} steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@v4 if: github.event_name == 'push' with: fetch-depth: 0 @@ -46,7 +46,7 @@ jobs: extended=true EOF - - uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 + - uses: dorny/paths-filter@v3 id: filter if: github.event_name == 'push' with: @@ -54,14 +54,10 @@ jobs: app: - 'project.bootstrap.yaml' - 'AGENTS.md' - - 'CLAUDE.md' - - '.devcontainer/**' + - 'CONTRIBUTING.md' + - '.github/PULL_REQUEST_TEMPLATE.md' - '.githooks/**' - '.github/workflows/**' - - 'pyproject.toml' - - 'src/**' - - 'tests/**' - - 'fixtures/**' - 'scripts/**' - 'docs/bootstrap/**' - 'README.md' @@ -69,8 +65,8 @@ jobs: ci: - 'project.bootstrap.yaml' - 'AGENTS.md' - - 'CLAUDE.md' - - '.devcontainer/**' + - 'CONTRIBUTING.md' + - '.github/PULL_REQUEST_TEMPLATE.md' - '.githooks/**' - '.github/workflows/**' - 'scripts/**' @@ -83,40 +79,40 @@ jobs: fast-checks: name: Fast Checks - runs-on: ['self-hosted', 'synology', 'shell-only', 'private'] + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] timeout-minutes: 15 needs: changes if: needs.changes.outputs.app == 'true' || needs.changes.outputs.ci == 'true' steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@v4 - name: Run fast checks run: bash scripts/ci/run-fast-checks.sh extended-checks: name: Extended Checks - runs-on: ['self-hosted', 'synology', 'shell-only', 'private'] + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] timeout-minutes: 20 needs: changes if: needs.changes.outputs.extended == 'true' || needs.changes.outputs.app == 'true' steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@v4 - name: Run extended validation run: bash scripts/ci/run-extended-validation.sh validate-secrets: name: Validate Secrets - runs-on: ['self-hosted', 'synology', 'shell-only', 'private'] + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] timeout-minutes: 10 steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@v4 - name: Scan repository for secret patterns run: bash scripts/check-detect-secrets.sh --all-files extended-validation-gate: name: Extended Validation Gate - runs-on: ['self-hosted', 'synology', 'shell-only', 'private'] + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] if: always() needs: - changes diff --git a/.github/workflows/pr-fast-ci.yml b/.github/workflows/pr-fast-ci.yml index ce8bca2..2c9ce48 100644 --- a/.github/workflows/pr-fast-ci.yml +++ b/.github/workflows/pr-fast-ci.yml @@ -2,7 +2,7 @@ name: PR Fast CI on: pull_request: - types: [opened, synchronize, reopened, ready_for_review] + types: [opened, edited, synchronize, reopened, ready_for_review] concurrency: group: pr-fast-${{ github.event.pull_request.number || github.ref }} @@ -12,6 +12,7 @@ permissions: contents: read env: + NODE_VERSION: '20' PYTHON_VERSION: '3.12' defaults: @@ -21,25 +22,20 @@ defaults: jobs: changes: name: Detect Relevant Changes - # Fork-controlled PR code never runs on persistent self-hosted runners. - runs-on: ubuntu-latest + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] outputs: app: ${{ steps.filter.outputs.app }} ci: ${{ steps.filter.outputs.ci }} - packaging: ${{ steps.filter.outputs.packaging }} steps: - - uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 + - uses: dorny/paths-filter@v3 id: filter with: filters: | app: - - 'pyproject.toml' - - 'src/**' - - 'tests/**' - 'project.bootstrap.yaml' - 'AGENTS.md' - - 'CLAUDE.md' - - '.devcontainer/**' + - 'CONTRIBUTING.md' + - '.github/PULL_REQUEST_TEMPLATE.md' - '.githooks/**' - '.github/workflows/**' - 'scripts/**' @@ -47,126 +43,112 @@ jobs: - 'README.md' - 'docs/**' ci: - - 'pyproject.toml' - - 'src/**' - - 'tests/**' - 'project.bootstrap.yaml' - 'AGENTS.md' - - 'CLAUDE.md' - - '.devcontainer/**' + - 'CONTRIBUTING.md' + - '.github/PULL_REQUEST_TEMPLATE.md' - '.githooks/**' - '.github/workflows/**' - 'scripts/**' - 'docs/bootstrap/**' - '.env.example' - 'CODEOWNERS' - packaging: - - 'pyproject.toml' - - '.github/workflows/release.yml' - - 'scripts/release/**' - - actionlint: - name: Actionlint - runs-on: ubuntu-latest - if: github.event.pull_request.draft == false - permissions: - contents: read - checks: write - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.event.pull_request.head.sha }} - persist-credentials: false - - uses: reviewdog/action-actionlint@a5524e1c19e62881d79c1f1b9b6f09f16356e281 # v1.65.2 - with: - reporter: github-pr-check - fail_level: error - - dependency-review: - name: Dependency Review - runs-on: ubuntu-latest - if: github.event.pull_request.draft == false - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.event.pull_request.head.sha }} - persist-credentials: false - - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 - with: - fail-on-severity: moderate fast-checks: name: Fast Checks - runs-on: ubuntu-latest + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] timeout-minutes: 15 needs: changes if: >- github.event.pull_request.draft == false && (needs.changes.outputs.app == 'true' || needs.changes.outputs.ci == 'true') steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - persist-credentials: false - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 - with: - python-version: ${{ env.PYTHON_VERSION }} + - name: Run fast checks run: bash scripts/ci/run-fast-checks.sh + validate-pr-description: + name: Validate PR Description + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] + timeout-minutes: 5 + if: github.event.pull_request.draft == false + env: + PR_BODY: ${{ github.event.pull_request.body }} + steps: + - name: Require generated PR template content + run: | + failed=0 + + require_line() { + local line="$1" + if ! grep -Fqx "$line" <<<"$PR_BODY"; then + echo "Missing required PR section: $line" + failed=1 + fi + } + + require_line "## Summary" + require_line "## Governing Issue" + require_line "## Validation" + require_line "## Bootstrap Governance" + require_line "## Merge Automation" + require_line "## Notes" + + if grep -Eiq 'Closes #$|#|what changed|why it changed|notable tradeoffs|migration or rollout notes|follow-up work if any' <<<"$PR_BODY"; then + echo "PR body still contains template placeholder text." + failed=1 + fi + + if ! grep -Eiq '(^|[[:space:]-])(((close[sd]?|fix(e[sd])?|resolve[sd]?|refs?|part[[:space:]]+of)[[:space:]]+)?(#|[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+#|https://github\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+/issues/)[0-9]+|no issue is linked|no linked issue|without a linked issue|no governing issue)' <<<"$PR_BODY"; then + echo "PR body must close/link an issue or explicitly explain why no issue is linked." + failed=1 + fi + + if ! grep -Eiq '(^|[[:space:]-])(\[[xX]\]|not run|not applicable|n/a)' <<<"$PR_BODY"; then + echo "PR body must include validation evidence, a checked validation item, or a reason validation was not run." + failed=1 + fi + + auto_merge_evidence="$(grep -Eiv '^[[:space:]]*-[[:space:]]+\[[[:space:]]\][[:space:]]' <<<"$PR_BODY" || true)" + if ! grep -Eiq 'auto-merge (is )?(enabled|armed)|enabled auto-merge|gh pr merge --auto|auto_merge|auto merge enabled|auto-merge (is )?(unavailable|unsafe|not available|not safe)|plan-limit|fallback merge-readiness' <<<"$auto_merge_evidence"; then + echo "PR body must state that the PR author enabled auto-merge, or explain why auto-merge is unavailable/unsafe." + failed=1 + fi + + exit "$failed" + validate-secrets: name: Validate Secrets - runs-on: ubuntu-latest + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] timeout-minutes: 10 if: github.event.pull_request.draft == false steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - uses: actions/checkout@v4 with: ref: ${{ github.event.pull_request.head.sha }} - persist-credentials: false - name: Scan repository for secret patterns run: bash scripts/check-detect-secrets.sh --all-files - package-build: - name: Package Build - runs-on: ubuntu-latest - timeout-minutes: 10 - needs: changes - if: >- - github.event.pull_request.draft == false && - needs.changes.outputs.packaging == 'true' - steps: - - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.event.pull_request.head.sha }} - persist-credentials: false - - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 - with: - python-version: ${{ env.PYTHON_VERSION }} - - name: Build distribution artifacts - run: bash scripts/release/dry-run.sh --skip-fast-checks - ci-gate: name: CI Gate - runs-on: ubuntu-latest + runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] if: always() needs: - changes - - actionlint - - dependency-review - fast-checks + - validate-pr-description - validate-secrets - - package-build steps: - name: Check required PR jobs env: RESULTS: >- changes=${{ needs.changes.result }} - actionlint=${{ needs.actionlint.result }} - dependency-review=${{ needs.dependency-review.result }} fast-checks=${{ needs.fast-checks.result }} + validate-pr-description=${{ needs.validate-pr-description.result }} validate-secrets=${{ needs.validate-secrets.result }} - package-build=${{ needs.package-build.result }} run: | failed=0 for entry in $RESULTS; do diff --git a/AGENTS.md b/AGENTS.md index 35edbc5..64e7bc8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,16 +2,24 @@ - Always work on a feature branch. Hooks block commits to `main` and `master`; enable them with `git config core.hooksPath .githooks`. - Stack baseline: Generic polyglot. -- Repo class: library. - CI baseline: fast PR checks stay cheap and shell-safe; extended validation runs on `main`, nightly, or manual dispatch. -- Self-hosted runner policy: shell-safe jobs may use `[self-hosted, synology, shell-only, private]`; anything needing Docker, service containers, browser infra, or `container:` must stay on GitHub-hosted runners. +- Self-hosted runner policy: shell-safe jobs must use `[self-hosted, linux, shell-only, public]`; native repos must use self-hosted runners for required automation, with Docker, service-container, browser, or `container:` workloads routed to dedicated self-hosted capability pools. - Add or update tests for every interactive, branching, or operator-facing behavior change. +- PRs must use the generated pull request template. The required PR gate validates summary, issue linkage, validation evidence, and risk notes. - Never commit real secrets, runtime auth, or machine-local env files. Use templates and GitHub environments instead. +## Kingdom Governance + +- Pheidon is the orchestrator and current gate for repo execution work. +- GitHub issues are the source of record for agent execution work. +- Worker agents should act from assigned or explicitly enabled issues, not free-roaming backlog grabs. +- If an agent authors a PR, that same agent may not approve it. This is a hard rule. +- Healthy PRs should converge toward auto-merge once required checks are green or intentionally skipped, approvals are satisfied, and no blocking review state remains. +- When GitHub plan limits make auto-merge unavailable for a private repo, use the fallback merge-readiness policy: required checks pass or are intentionally skipped, approvals and conversation resolution are satisfied, no blocking review state remains, and a maintainer performs the merge manually. +- PRs should link and close their governing issue where possible so issue state remains the durable work contract. + ## Local Conventions - Keep scope tight and favor predictable templates over clever scaffolding. - Treat `project.bootstrap.yaml` as the source of truth for repo governance, environments, CI policy, and home profile sync. -- If OpenClaw local skills are available, use the `omt-bootstrap` skill for manifest-first bootstrap work instead of rediscovering the workflow. - Review `docs/bootstrap/onboarding.md` before first merge to confirm reviewers, runner labels, and environment gates match the project. -- The R1-R6 GitHub issues with the `release-v0.1` label drive the public v0.1.0 release. diff --git a/CLAUDE.md b/CLAUDE.md index 6280454..01707c4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,25 +1,13 @@ # CLAUDE.md -## Project Map +## Project Context -- `project.bootstrap.yaml`: source of truth for bootstrap policy -- `.github/workflows/`: generated fast and extended CI lanes -- `scripts/claude-cloud/setup.sh`: first-party Claude Code on the web setup script -- `.github/workflows/claude.yml`: opt-in Claude GitHub Action for manual or `@claude` review flows -- `.devcontainer/devcontainer.json`: interactive Claude Code workspace baseline -- `.github/workflows/`: repo CI and review workflows -- `scripts/ci/`: bootstrap CI entrypoints when this repo uses the generated workflow lane -- `scripts/claude/setup-devcontainer.sh`: installs repo dependencies inside the devcontainer -- `.githooks/pre-commit`: branch and env-file guardrail when local hooks are bootstrap-managed -- `docs/bootstrap/onboarding.md`: operator checklist for repo/governance setup -- `docs/bootstrap/claude-environment.md`: Claude setup guide for hosted, interactive, and GitHub-hosted use +- Repository: OMT-Global/mailplus-intelligence +- Default branch: main +- Required PR checks: CI Gate ## Guardrails -- Keep `CI Gate` as the single required PR status check. -- Use one approval plus code owners on `main` unless the manifest explicitly changes it. -- `stage` and `prod` environments require reviewers and prevent self-review by default. -- Home-level Codex and Claude profile sync is managed by the bootstrap tool, not by ad-hoc manual edits. -- Claude Code on the web should use the repo-managed setup script and keep network access limited by default. -- The generated Claude GitHub Action is a separate review lane. It must not become a required status check. -- Treat the devcontainer as a trusted-repo workspace. Do not mount extra secrets beyond the persisted `~/.claude` profile unless you explicitly need them. +- Use AGENTS.md and docs/bootstrap/onboarding.md as the governing repo policy. +- Keep Claude automation separate from required PR status checks unless the manifest explicitly changes that contract. +- Do not commit secrets or machine-local environment files. diff --git a/CODEOWNERS b/CODEOWNERS index bd56e55..1109d02 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1 @@ -# OMT-Global code ownership -* @OMT-Global/omt-codeowners \ No newline at end of file +* @jmcte diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0d3d504..424a3d2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,50 +1,30 @@ # Contributing -Thanks for helping improve MailPlus Intelligence. +Contributions should start from a GitHub issue that is assigned or explicitly enabled by Pheidon. Keep changes scoped to that issue, work on a feature branch, and link the issue from the pull request. -## Development Setup +## Local Setup -Use Python 3.12 or newer: +- Install dependencies for the selected stack before changing code. +- Enable repo hooks with `git config core.hooksPath .githooks`; they block direct commits to `main` and catch committed runtime env files. +- Use `project.bootstrap.yaml` as the source of truth for governance, CI, environments, and bootstrap-managed guidance files. -```bash -python3.12 -m venv .venv -source .venv/bin/activate -python -m pip install -e . -``` +## Change Expectations -Run the fast validation gate before opening a pull request: +- Keep implementation changes minimal and relevant to the governing issue. +- Add or update tests for interactive, branching, or operator-facing behavior changes. +- Keep fast PR checks cheap and shell-safe; move heavyweight validation to `scripts/ci/run-extended-validation.sh`. +- Do not commit real secrets, runtime auth, generated credentials, caches, or machine-local env files. -```bash -bash scripts/ci/run-fast-checks.sh -``` +## Validation -Before staging local MailPlus fixtures, generated metadata, or cache-related -changes, run the broader local scan so untracked non-ignored files are checked: - -```bash -bash scripts/check-detect-secrets.sh --all-local -``` +- Run the relevant local checks before opening a PR. +- For this bootstrap contract, the required PR check surface is `CI Gate`. +- Document any skipped checks in the PR with a concrete reason. ## Pull Requests -- Keep changes focused and reviewable. -- Add or update tests for interactive, branching, or operator-facing behavior. -- Keep `CI Gate` passing. -- Do not include generated databases, local caches, runtime auth, mailbox - exports, raw message bodies, attachment payloads, or machine-local files. - -## Privacy And Fixtures - -Fixtures and examples must be synthetic or fully redacted. Use reserved domains -such as `example.com` or `example.test`, and avoid values that identify real -people, accounts, hosts, messages, or credentials. - -Review `docs/privacy-redaction-boundaries.md` before adding data fixtures, -semantic outputs, logs, or documentation examples. - -## Good First Issues - -Good newcomer tasks are tracked with the -[`good first issue` label](https://github.com/OMT-Global/mailplus-intelligence/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). -Prefer fixture-mode changes, docs clarifications, or tests that do not require -live MailPlus credentials. +- Use `.github/PULL_REQUEST_TEMPLATE.md`. +- Link the governing issue with a closing keyword when the PR should close it. +- PR authors may not approve their own PRs. +- A healthy PR should converge toward auto-merge after required checks pass or are intentionally skipped, approvals are satisfied, and no blocking review state remains. +- When GitHub plan limits make auto-merge unavailable for a private repo, use the fallback merge-readiness policy: required checks pass or are intentionally skipped, approvals and conversation resolution are satisfied, no blocking review state remains, and a maintainer performs the merge manually. diff --git a/SECURITY.md b/SECURITY.md index 7112b64..37f0a8c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,28 +1,15 @@ # Security Policy -## Supported Versions +## Supported Surface -This project is pre-1.0. Security fixes are applied to the default branch. +This repository follows the bootstrap-managed security baseline for OMT-Global/mailplus-intelligence. -## Reporting A Vulnerability +## Reporting -Please do not open public issues for suspected vulnerabilities, exposed secrets, -or privacy-sensitive data handling problems. +Open a private security advisory or contact the repository maintainers before disclosing a vulnerability publicly. -Report security concerns through GitHub private vulnerability reporting when -available for this repository. If private reporting is unavailable, contact the -repository owner with a minimal description of the issue and the affected -version or commit. +## Baseline -Expected response: the maintainer will acknowledge a complete report within -seven calendar days and will provide a remediation or disclosure plan once the -impact is understood. Please include enough reproduction detail to verify the -issue without including live credentials, raw mail, mailbox exports, or other -private payloads. - -## Data Handling Scope - -MailPlus Intelligence must not store raw mail bodies, attachment payloads, -credential material, session state, mailbox exports, or local runtime caches in -the repository. See `docs/privacy-redaction-boundaries.md` for the project data -boundary and redaction rules. +- Dependabot policy: enabled +- Secret scanning hints: enabled +- Generated hooks and CI helpers must not require committed secrets or machine-local environment files. diff --git a/docs/bootstrap/claude-environment.md b/docs/bootstrap/claude-environment.md index 95c49de..b5d0386 100644 --- a/docs/bootstrap/claude-environment.md +++ b/docs/bootstrap/claude-environment.md @@ -1,61 +1,18 @@ # Claude Environment -Claude Code on the web provides a first-party cloud environment comparable to Codex Web. This bootstrap prepares the hosted path first, then adds optional local and GitHub-native alternatives: - -- First-party hosted sessions at `claude.ai/code` -- Interactive containerized work with `.devcontainer/devcontainer.json` -- GitHub-hosted automation with `.github/workflows/claude.yml` - ## Project - Product name: `MailPlus Intelligence` - Repository: `OMT-Global/mailplus-intelligence` - Manifest: `project.bootstrap.yaml` -## Claude Code On The Web - -- Hosted entrypoint: `https://claude.ai/code` -- Repo: `OMT-Global/mailplus-intelligence` -- Setup script: `bash scripts/claude-cloud/setup.sh` -- Network access: start with limited access; only expand it when a task truly needs more than registries and GitHub -- Environment variables: configure them in the Claude environment UI as `.env`-style key-value pairs -- GitHub integration: connect GitHub, install the Claude GitHub App, then pick this repo as an allowed target -- Repo guidance: Claude on the web reads `CLAUDE.md` from the repository - -## Teleport And Remote Sessions - -- Start a hosted task from the terminal with `claude --remote "your task"` -- Pull a hosted session back into the terminal with `claude --teleport` -- Hosted tasks clone the default branch unless you specify a branch in the prompt -- Teleport requires a clean git state and the same repository/account pairing - -## Interactive Devcontainer +## Enabled Surfaces -- Open the repo in a devcontainer-capable editor and reopen in container. -- The container installs the Claude Code feature plus repo dependencies via `bash scripts/claude/setup-devcontainer.sh`. -- `~/.claude` is mounted into the container so Claude Code auth persists between sessions. -- Only use this with trusted repositories. Mounted Claude credentials are available inside the container. - -## GitHub Action - -- Workflow file: `.github/workflows/claude.yml` -- Runner: `ubuntu-latest` -- Triggers: - - manual `workflow_dispatch` - - PR or issue comments containing `@claude` - - review comments or review bodies containing `@claude` -- Auth: - - preferred: run `/install-github-app` in Claude Code as a repo admin - - fallback: add a repository secret named `ANTHROPIC_API_KEY` +- Claude Code on the web with `bash scripts/claude-cloud/setup.sh` +- Interactive devcontainer through `.devcontainer/devcontainer.json` +- GitHub-hosted Claude workflow at `.github/workflows/claude.yml` ## Guardrails -- Keep the Claude workflow out of the required PR check set. The required checks are `CI Gate`. -- Prefer Claude Code on the web for long-running async review or fix tasks; use the devcontainer when you need a local interactive container. -- Treat the devcontainer as a trusted-repo workspace because the mounted `~/.claude` profile is available inside the container. -- Do not relax the action to allow non-write users on public repos unless you intentionally accept the prompt-injection risk. -- Keep Claude review and automation on GitHub-hosted runners; do not move it onto the self-hosted shell-only fleet. - -## Project - -- Default branch: `main` +- Keep Claude automation out of the required PR check set unless the manifest explicitly changes branch protection. +- Prefer repo-scoped secrets and avoid mounting additional host credentials into the devcontainer. diff --git a/docs/bootstrap/next-steps.md b/docs/bootstrap/next-steps.md index 1d22175..555bd7a 100644 --- a/docs/bootstrap/next-steps.md +++ b/docs/bootstrap/next-steps.md @@ -1,13 +1,7 @@ # Next Steps -- Treat the Python 3.12 package, SQLite fixture runtime, and CI scripts as the - established baseline; update `project.bootstrap.yaml` before changing their - governance contract. -- Run `bash scripts/ci/run-fast-checks.sh` before each PR. It includes the - executable quickstart/runbook smoke contract and requires no live credentials. -- Periodically verify CODEOWNERS, environment reviewers, runner labels, and the - required `CI Gate` check against `docs/bootstrap/onboarding.md`. -- Re-run `bootstrap plan --manifest ./project.bootstrap.yaml` after major - manifest changes to confirm intended drift. -- Finish the remaining Phase A blockers in issue #98 before claiming a live or - production-verified MailPlus capability. +- Add the primary runtime and package manifest for this project. +- Tighten `scripts/ci/run-fast-checks.sh` and `scripts/ci/run-extended-validation.sh` once the toolchain is known. + +- Review CODEOWNERS, environment reviewers, and required PR checks before the first merge. +- Re-run `bootstrap plan --manifest ./project.bootstrap.yaml` after major manifest changes to confirm intended drift. \ No newline at end of file diff --git a/docs/bootstrap/onboarding.md b/docs/bootstrap/onboarding.md index 19c1cd7..47abd21 100644 --- a/docs/bootstrap/onboarding.md +++ b/docs/bootstrap/onboarding.md @@ -12,7 +12,10 @@ Use this checklist after the first bootstrap render or whenever `project.bootstr - Confirm branch protection or rulesets on `main` require one approval, code owner review, and approval from someone other than the most recent pusher. - Confirm branch protection points at the `CI Gate` status. -- Confirm `delete branch on merge` and `allow auto-merge` are enabled. +- Confirm `CONTRIBUTING.md` and `.github/PULL_REQUEST_TEMPLATE.md` are present as the required contributor and PR guidance surfaces. +- Confirm the pull request template is present and PR Fast CI validates the required PR description sections before CI Gate can pass. +- Confirm `delete branch on merge` and `allow auto-merge` are enabled when the GitHub plan supports them; otherwise record the plan-limit evidence and use the fallback merge-readiness policy. +- Fallback merge readiness requires passing or intentionally skipped required checks, satisfied approvals, resolved conversations, no blocking review state, and a manual maintainer merge. @@ -25,24 +28,37 @@ Use this checklist after the first bootstrap render or whenever `project.bootstr ## Runner Policy -- Shell-safe jobs may use `[self-hosted, synology, shell-only, private]`. -- Docker, service-container, browser, and `container:` workloads stay on GitHub-hosted runners. +- Shell-safe jobs must use `[self-hosted, linux, shell-only, public]`. +- Native repos must use self-hosted runners for required automation; Docker, service-container, browser, and `container:` workloads require a dedicated self-hosted runner pool with matching capability labels. - Keep PR checks cheap. Add heavy validation to `scripts/ci/run-extended-validation.sh` instead of the PR lane. -- See `docs/bootstrap/ci-validation.md` for the current fast versus extended validation contract. +- Consume shared security, release, and AI attestation workflows from the control-plane repo once those contracts are pinned for production use. -## Home Profiles +## Contributor And PR Guidance -- Run `bootstrap apply home --manifest ./project.bootstrap.yaml` after reviewing the bundled profile content. -- The bootstrap manages portable Codex and Claude assets only. Auth, sessions, caches, and machine-local state stay unmanaged. +- `CONTRIBUTING.md` defines the contributor workflow, branch expectations, validation expectations, and secret-handling baseline. +- `.github/PULL_REQUEST_TEMPLATE.md` defines the standard PR shape: summary, governing issue link, validation notes, and bootstrap governance checklist. +- To retrofit an existing bootstrapped repo, add `CONTRIBUTING.md` and `.github/PULL_REQUEST_TEMPLATE.md` to `repo.managedPaths` when that repo restricts managed paths, then run `bootstrap apply repo --manifest ./project.bootstrap.yaml`. +- Keep these files repo-generic unless project metadata or the manifest requires a stricter local rule. + +## Fleet Reconciliation + +- Run `bootstrap reconcile --workspace-root ~/src --report bootstrap-reconcile.json` first; this is plan-only and does not write files. +- Add `--org OMT-Global` when OpenClaw should enumerate GitHub repos first; missing local checkouts or repos without `project.bootstrap.yaml` are skipped and reported. +- Use `--repo ` as the initial allowlist when onboarding daily OpenClaw reconciliation. +- Use `--apply-repo --create-pr` for unattended repo drift so generated changes go through draft PRs instead of default-branch pushes. +- Use `--apply-github` only after the report shape is trusted because it mutates repository settings, environments, branch protection, and labels directly through the GitHub API. +- Dirty target worktrees are blocked and reported instead of being overwritten. -## OpenClaw Skill +## Release Standard -- If local OpenClaw skills are installed, use the `omt-bootstrap` skill for manifest upgrades, plan/apply flows, and v1-to-v2 normalization guidance. +- Use immutable exact SemVer tags such as `v1.2.3` as the source of truth. +- Automatically advance `v1.2` and `v1` to the newest compatible exact tag; never retag an exact release. +- Cut patch releases from `release/X.Y` when you maintain older minors; cut new minors and majors from `main`. -## Claude Setup -- First-party Claude web sessions should use `bash scripts/claude-cloud/setup.sh` in `claude.ai/code`. -- Interactive Claude work is prepared through `.devcontainer/devcontainer.json`. -- GitHub-hosted Claude automation lives in `.github/workflows/claude.yml` and is intentionally separate from the required PR checks. -- Finish GitHub-side auth by running `/install-github-app` in Claude Code or adding `ANTHROPIC_API_KEY` as a repo secret. + +## Home Profiles + +- Run `bootstrap apply home --manifest ./project.bootstrap.yaml` after reviewing the bundled profile content. +- The bootstrap manages portable Codex assets only. Auth, sessions, caches, and machine-local state stay unmanaged. diff --git a/docs/bootstrap/versioning.md b/docs/bootstrap/versioning.md new file mode 100644 index 0000000..93038b6 --- /dev/null +++ b/docs/bootstrap/versioning.md @@ -0,0 +1,48 @@ +# Release Versioning + +This bootstrap standardizes on Semantic Versioning with immutable exact tags and automatically promoted compatibility aliases. + +## Tag Rules + +- Exact release tags are immutable: `v1.2.3` +- Minor compatibility tags move forward automatically: `v1.2` +- Major compatibility tags move forward automatically: `v1` + +Consumers should prefer `v1` for the default compatibility channel, `v1.2` when they need to stay on one minor line, and an exact tag or SHA when they need full reproducibility. + +## Branch Rules + +- `main` is the next minor or major release train. +- `release/X.Y` branches are maintenance lines for patch releases on older minors. +- Promote fixes forward: oldest supported `release/X.Y` first, then newer maintenance branches, then `main`. + +## Automation + +- `.github/workflows/release-tag.yml` runs when an exact SemVer tag matching `v*.*.*` is pushed. +- `scripts/ci/run-release-verification.sh` runs the repo release gate before publication. +- `scripts/ci/run-release-version.sh` validates that the repo version surfaces match the pushed tag before build and publish. +- `scripts/ci/run-release-build.sh` populates the release artifact directory and writes SHA256 checksums when enabled. +- `scripts/ci/run-release-publish.sh` is the repo hook for artifact publication; the generated default is a no-op until the repo needs more than GitHub releases. +- The shared reusable release workflow creates or updates the GitHub release and then advances the floating compatibility tags when enabled in `project.bootstrap.yaml`. + +## Version Validation + +- Version bumps land in a normal pull request before the release tag is pushed; the workflow never creates post-tag commits. +- Configure version surfaces under `release.versions` in `project.bootstrap.yaml` with a `type` of `npm`, `python`, or `container` and the file `path`. +- `scripts/ci/run-release-version.sh` fails the release when a configured `package.json` or `pyproject.toml` version does not equal the tag (with the `v` prefix stripped). Container surfaces are derived from the tag at publish time. +- With no configured surfaces the hook prints an explicit no-op rather than silently passing. + +## Release Artifacts + +- The default release artifact directory is `dist/release/`. +- `scripts/ci/run-release-build.sh` is where repo-specific build steps populate that directory; with no artifacts it prints an explicit no-op message instead of failing silently. +- Checksum generation is `sha256`; when set to `sha256` a `SHA256SUMS` file is written alongside the artifacts. +- The reusable workflow uploads every file in the artifact directory to the GitHub Release. +- SBOM/provenance is `optional` and is designed into the manifest for repos that opt in. + +## Release Notes + +- Release notes are generated automatically for every exact tag from changes since the previous exact SemVer tag. +- The default implementation uses GitHub generated notes; `.github/release.yml` maps bootstrap labels to categories. +- Override categories under `release.changelog.categories` in `project.bootstrap.yaml`. The default categories are Features, Fixes, Operations, and Documentation, with everything else under Other Changes. +- The reusable workflow writes the notes to `dist/release/RELEASE_NOTES.md` before creating the GitHub Release. diff --git a/project.bootstrap.yaml b/project.bootstrap.yaml index 53369fe..24ffb99 100644 --- a/project.bootstrap.yaml +++ b/project.bootstrap.yaml @@ -56,6 +56,98 @@ github: - pattern: "*" owners: - "@jmcte" + issueLabels: + - name: area:frontend + color: 1f77b4 + description: Frontend and user-interface work. + - name: area:api + color: 2ca02c + description: API contracts, endpoints, and integrations. + - name: area:data + color: 9467bd + description: Data models, persistence, migration, and analytics work. + - name: area:ledger + color: 8c564b + description: Ledger, accounting, transaction, or reconciliation work. + - name: area:rules + color: bcbd22 + description: Domain rules, policy logic, and decision engines. + - name: area:ai + color: 17becf + description: AI, agents, prompts, and model integration work. + - name: area:infra + color: 7f7f7f + description: Infrastructure, CI, deployment, and operations work. + - name: area:security + color: d62728 + description: Security-sensitive implementation or hardening work. + - name: area:accessibility + color: e377c2 + description: Accessibility and inclusive UX work. + - name: area:qa + color: ff7f0e + description: Quality assurance, test coverage, and release validation. + - name: risk:low + color: 0e8a16 + description: Low implementation or operational risk. + - name: risk:medium + color: fbca04 + description: Moderate implementation or operational risk. + - name: risk:high + color: d93f0b + description: High implementation or operational risk. + - name: risk:domain + color: "5319e7" + description: Domain correctness risk requiring subject-matter review. + - name: risk:security + color: b60205 + description: Security risk requiring explicit review. + - name: risk:prod + color: "000000" + description: Production impact or rollout risk. + - name: status:needs-spec + color: cfd3d7 + description: Needs clearer scope, acceptance criteria, or constraints. + - name: status:ready-for-agent + color: 0e8a16 + description: Ready for assigned agent implementation. + - name: status:agent-building + color: 1d76db + description: Agent implementation is in progress. + - name: status:needs-review + color: fbca04 + description: Needs review before merge or closure. + - name: status:needs-human-approval + color: d93f0b + description: Needs explicit human approval before proceeding. + - name: status:ready-to-merge + color: 0e8a16 + description: Ready to merge after required checks pass. + - name: status:blocked + color: b60205 + description: Blocked by a dependency, decision, credential, or access gate. + - name: review:product + color: 0052cc + description: Needs product review. + - name: review:architecture + color: "5319e7" + description: Needs architecture review. + - name: review:security + color: b60205 + description: Needs security review. + - name: review:tax + color: d4c5f9 + description: Needs tax review. + - name: review:legal + color: c2e0c6 + description: Needs legal review. + - name: review:accessibility + color: e99695 + description: Needs accessibility review. + - name: review:release + color: f9d0c4 + description: Needs release review. + flowGovernance: false autoMerge: true deleteBranchOnMerge: true requiredApprovals: 1 @@ -88,22 +180,56 @@ ci: - template-review - fixture-regression nightlyCron: 0 7 * * * + additionalWorkflows: [] workflows: prFastCi: true extendedValidation: true claude: true pagesDeploy: false ci: false - extras: &a1 [] - additionalWorkflows: *a1 + extras: [] + appPaths: [] + ciPaths: [] + extendedPaths: [] + macosCheck: + enabled: false + paths: [] + runsOn: + - macos-14 + command: xcodebuild -version + customScripts: {} + dependabot: + enabled: true + securityUpdates: true + versionUpdates: true + ecosystems: + - packageEcosystem: npm + directory: / + interval: weekly + groupMinorAndPatch: true + ignoreMajorUpdates: true + - packageEcosystem: github-actions + directory: / + interval: weekly + groupMinorAndPatch: false + ignoreMajorUpdates: true + aiAttestation: + enabled: false + artifactName: ai-attestation + retentionDays: 90 + provider: unknown + model: unknown + promptHash: unknown + reusableWorkflowRepo: OMT-Global/bootstrap + reusableWorkflowRef: refs/heads/main agents: manageCodexHome: true manageClaudeHome: true - codexProfile: default claudeProfile: default enableClaudeWebEnvironment: true enableClaudeDevcontainer: true enableClaudeGitHubAction: true + codexProfile: default sharedSkills: [] capabilities: pages: diff --git a/scripts/check-detect-secrets.sh b/scripts/check-detect-secrets.sh index 079b50e..f127c75 100755 --- a/scripts/check-detect-secrets.sh +++ b/scripts/check-detect-secrets.sh @@ -1,136 +1,80 @@ -#!/usr/bin/env bash -set -euo pipefail - -mode="${1:-"--all-files"}" -ignore_globs=("scripts/check-detect-secrets.sh" ".env.example") -if [[ -f .detect-secrets-ignore ]]; then - while IFS= read -r ignore_glob; do - if [[ -z "$ignore_glob" ]]; then - continue - fi - if [[ "${ignore_glob:0:1}" == "#" ]]; then - continue - fi - ignore_globs+=("$ignore_glob") - done < .detect-secrets-ignore -fi - -should_skip_file() { - local candidate="$1" - local ignore_glob - for ignore_glob in "${ignore_globs[@]}"; do - case "$candidate" in - $ignore_glob) - return 0 - ;; - esac - done - return 1 -} - -append_tracked_files() { - while IFS= read -r -d '' file; do - files+=("$file") - done < <(git ls-files -z) -} - -append_staged_files() { - while IFS= read -r -d '' file; do - files+=("$file") - done < <(git diff --cached --name-only --diff-filter=ACMR -z) -} - -append_untracked_files() { - while IFS= read -r -d '' file; do - files+=("$file") - done < <(git ls-files --others --exclude-standard -z) -} - -files=() -case "$mode" in - --staged) - append_staged_files - ;; - --all-files) - append_tracked_files - ;; - --all-files-with-untracked | --all-local | --local) - append_tracked_files - append_untracked_files - ;; - *) - echo "Usage: $0 [--all-files|--staged|--all-files-with-untracked|--all-local|--local]" >&2 - exit 2 - ;; -esac - -if [[ "${#files[@]}" -eq 0 ]]; then - echo "No files to scan." - exit 0 -fi - -content_patterns=( - 'ghp_' - 'github_pat_' - 'sk-live-' - 'sk-proj-' - 'AKIA[0-9A-Z]{16}' - 'BEGIN (RSA|OPENSSH|EC) PRIVATE KEY' - 'ANTHROPIC_API_KEY=' - 'OPENAI_API_KEY=' - 'SUDO_PASS=' - 'BW_SESSION=' -) - -mailplus_content_patterns=( - 'https?://[^[:space:])>"]*(oauth|authorize|token)[^[:space:])>"]*(token|code|key|secret|signature|sig|jwt|session|auth)=' - 'https?://[^[:space:])>"]*(reset|recovery|recover-password|password-reset|magic|login)[^[:space:])>"]*(token|code|key|secret|signature|sig|jwt|session|auth)=' - 'https?://[^[:space:])>"]*(pay|payment|checkout|invoice|billing)[^[:space:])>"]*(token|code|key|secret|signature|sig|jwt|session|auth)=' -) - -mailplus_path_patterns=( - '(^|/)\.env(\.|$)' - '\.eml$' - '\.mbox$' - '\.(db|sqlite|sqlite3)(-(wal|shm))?$' - '(^|/)(exports|mailplus-exports)(/|$)' - '(^|/)(mailplus|selected-text|semantic|metadata)[^/]*\.(cache|log)$' -) - -allowed_synthetic_pattern='(example\.(com|org|net|test)|\[REDACTED_[A-Z_]+\])' - -tmp_file="$(mktemp)" -trap 'rm -f "$tmp_file"' EXIT - -for file in "${files[@]}"; do - if [[ ! -f "$file" ]] || should_skip_file "$file"; then - continue - fi - printf '%s\n' "$file" >>"$tmp_file" -done - -failed=0 -while IFS= read -r file; do - for pattern in "${mailplus_path_patterns[@]}"; do - if [[ "$file" =~ $pattern ]]; then - echo "Potential MailPlus export/cache artifact '$pattern' found at $file" >&2 - failed=1 + #!/usr/bin/env bash + set -euo pipefail + + mode="${1:-"--all-files"}" + ignore_globs=("scripts/check-detect-secrets.sh") + if [[ -f .detect-secrets-ignore ]]; then + while IFS= read -r ignore_glob; do + if [[ -z "$ignore_glob" ]]; then + continue + fi + if [[ "${ignore_glob:0:1}" == "#" ]]; then + continue + fi + ignore_globs+=("$ignore_glob") + done < .detect-secrets-ignore fi - done - for pattern in "${content_patterns[@]}"; do - if grep -E -n "$pattern" "$file" >/dev/null 2>&1; then - echo "Potential secret pattern '$pattern' found in $file" >&2 - failed=1 + should_skip_file() { + local candidate="$1" + local ignore_glob + for ignore_glob in "${ignore_globs[@]}"; do + case "$candidate" in + $ignore_glob) + return 0 + ;; + esac + done + return 1 + } + + files=() + if [[ "$mode" == "--staged" ]]; then + while IFS= read -r -d '' file; do + files+=("$file") + done < <(git diff --cached --name-only --diff-filter=ACMR -z) + else + while IFS= read -r -d '' file; do + files+=("$file") + done < <(git ls-files -z) fi - done - for pattern in "${mailplus_content_patterns[@]}"; do - if grep -E -n "$pattern" "$file" | grep -Ev "$allowed_synthetic_pattern" >/dev/null 2>&1; then - echo "Potential MailPlus link leak pattern '$pattern' found in $file" >&2 - failed=1 + if [[ "${#files[@]}" -eq 0 ]]; then + echo "No files to scan." + exit 0 fi - done -done <"$tmp_file" -exit "$failed" + patterns=( + 'ghp_' + 'github_pat_' + 'sk-live-' + 'sk-proj-' + 'AKIA[0-9A-Z]{16}' + 'BEGIN (RSA|OPENSSH|EC) PRIVATE KEY' + 'OPENAI_API_KEY=' + 'SUDO_PASS=' + 'BW_SESSION=' + ) + + tmp_file="$(mktemp)" + trap 'rm -f "$tmp_file"' EXIT + + for file in "${files[@]}"; do + if [[ ! -f "$file" ]] || should_skip_file "$file"; then + continue + fi + printf '%s +' "$file" >>"$tmp_file" + done + + failed=0 + while IFS= read -r file; do + for pattern in "${patterns[@]}"; do + if grep -E -n "$pattern" "$file" >/dev/null 2>&1; then + echo "Potential secret pattern '$pattern' found in $file" >&2 + failed=1 + fi + done + done <"$tmp_file" + + exit "$failed" diff --git a/scripts/ci/run-extended-validation.sh b/scripts/ci/run-extended-validation.sh index b3a15de..fd07826 100755 --- a/scripts/ci/run-extended-validation.sh +++ b/scripts/ci/run-extended-validation.sh @@ -1,16 +1,4 @@ #!/usr/bin/env bash set -euo pipefail bash scripts/ci/run-fast-checks.sh - -if command -v python3.12 >/dev/null 2>&1; then - PYTHON_BIN=python3.12 -else - PYTHON_BIN=python3 -fi - -REPORT_PATH="${TMPDIR:-/tmp}/mailplus-intelligence-evaluation.json" -PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=src "$PYTHON_BIN" scripts/evaluate.py \ - --fixtures-dir fixtures \ - --report-json "$REPORT_PATH" - -echo "Evaluation report: $REPORT_PATH" +echo "No extended checks configured for the generic archetype yet." diff --git a/scripts/ci/run-fast-checks.sh b/scripts/ci/run-fast-checks.sh index 494cfe1..b0fe54e 100755 --- a/scripts/ci/run-fast-checks.sh +++ b/scripts/ci/run-fast-checks.sh @@ -1,20 +1,4 @@ #!/usr/bin/env bash set -euo pipefail - -if command -v python3.12 >/dev/null 2>&1; then - PYTHON_BIN=python3.12 -else - PYTHON_BIN=python3 -fi - -"$PYTHON_BIN" - <<'PY' -import sys - -if sys.version_info < (3, 12): - raise SystemExit("Python 3.12 or newer is required") -PY - -bash scripts/check-detect-secrets.sh --all-files -bash scripts/ci/check-ci-contract.sh -PYTHONPATH=src "$PYTHON_BIN" scripts/ci/docs_smoke.py -PYTHONPATH=src "$PYTHON_BIN" -m unittest discover -s tests -v +echo "Generic archetype selected." +echo "Add project-specific scripts and tighten scripts/ci/run-fast-checks.sh when the stack is finalized." diff --git a/scripts/ci/run-release-build.sh b/scripts/ci/run-release-build.sh new file mode 100644 index 0000000..ef98f0a --- /dev/null +++ b/scripts/ci/run-release-build.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -euo pipefail + +artifact_dir="dist/release" +mkdir -p "${artifact_dir}" + +# Add repo-specific build steps above this line to populate ${artifact_dir} +# with downloadable release assets before checksums are generated. + +mapfile -t artifacts < <(find "${artifact_dir}" -maxdepth 1 -type f ! -name SHA256SUMS | sort) +if [[ ${#artifacts[@]} -eq 0 ]]; then + echo "No release artifacts were produced in ${artifact_dir}." + echo "This repo ships no downloadable assets; add build steps to scripts/ci/run-release-build.sh when it does." + exit 0 +fi +( + cd "${artifact_dir}" + : > SHA256SUMS + for entry in "${artifacts[@]}"; do + sha256sum -- "${entry#"${artifact_dir}/"}" >> SHA256SUMS + done +) +echo "Wrote ${artifact_dir}/SHA256SUMS for ${#artifacts[@]} artifact(s)." diff --git a/scripts/ci/run-release-publish.sh b/scripts/ci/run-release-publish.sh new file mode 100644 index 0000000..95f03ad --- /dev/null +++ b/scripts/ci/run-release-publish.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "No repo-specific artifact publish step is configured." +echo "Create exact release tags such as v1.2.3; the reusable release workflow handles GitHub release publication and floating tag promotion." diff --git a/scripts/ci/run-release-verification.sh b/scripts/ci/run-release-verification.sh new file mode 100644 index 0000000..92b4432 --- /dev/null +++ b/scripts/ci/run-release-verification.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +bash scripts/ci/run-fast-checks.sh +bash scripts/ci/run-extended-validation.sh diff --git a/scripts/ci/run-release-version.sh b/scripts/ci/run-release-version.sh new file mode 100644 index 0000000..04146d1 --- /dev/null +++ b/scripts/ci/run-release-version.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -euo pipefail + +tag="${GITHUB_REF_NAME:-}" +if [[ -z "${tag}" ]]; then + echo "GITHUB_REF_NAME is required to validate release versions." >&2 + exit 1 +fi +prefix="v" +version="${tag#"${prefix}"}" + +echo "No release version surfaces are configured in project.bootstrap.yaml." +echo "Skipping version validation for ${tag}; version bump pull requests must merge before the release tag is pushed." diff --git a/scripts/claude-cloud/setup.sh b/scripts/claude-cloud/setup.sh index 128337e..8d3ba4d 100755 --- a/scripts/claude-cloud/setup.sh +++ b/scripts/claude-cloud/setup.sh @@ -1,28 +1,5 @@ - #!/usr/bin/env bash - set -euo pipefail +#!/usr/bin/env bash +set -euo pipefail - if ! command -v gh >/dev/null 2>&1; then - apt-get update - apt-get install -y gh -fi - -if [[ -f package-lock.json ]]; then - npm ci --prefer-offline --no-audit --no-fund -elif [[ -f pnpm-lock.yaml ]]; then - corepack enable - pnpm install --frozen-lockfile -elif [[ -f yarn.lock ]]; then - corepack enable - yarn install --immutable -elif [[ -f package.json ]]; then - npm install --prefer-offline --no-audit --no-fund -fi - -if [[ -f pyproject.toml ]]; then - if [[ ! -d .venv ]]; then - python3 -m venv .venv - fi - source .venv/bin/activate - python -m pip install --upgrade pip setuptools wheel - python -m pip install -e ".[dev]" >/dev/null 2>&1 || python -m pip install -e . >/dev/null 2>&1 || true -fi +bash scripts/codex-cloud/setup.sh +echo "Claude cloud setup complete for OMT-Global/mailplus-intelligence." diff --git a/scripts/claude/setup-devcontainer.sh b/scripts/claude/setup-devcontainer.sh index 2fe8264..7f3168c 100755 --- a/scripts/claude/setup-devcontainer.sh +++ b/scripts/claude/setup-devcontainer.sh @@ -1,26 +1,4 @@ - #!/usr/bin/env bash - set -euo pipefail +#!/usr/bin/env bash +set -euo pipefail - git config --global --add safe.directory "$(pwd)" - -if [[ -f package-lock.json ]]; then - npm ci --prefer-offline --no-audit --no-fund -elif [[ -f pnpm-lock.yaml ]]; then - corepack enable - pnpm install --frozen-lockfile -elif [[ -f yarn.lock ]]; then - corepack enable - yarn install --immutable -elif [[ -f package.json ]]; then - npm install --prefer-offline --no-audit --no-fund -fi - -if [[ -f pyproject.toml ]]; then - if [[ ! -d .venv ]]; then - python3 -m venv .venv - fi - - source .venv/bin/activate - python -m pip install --upgrade pip setuptools wheel - python -m pip install -e ".[dev]" >/dev/null 2>&1 || python -m pip install -e . >/dev/null 2>&1 || true -fi +bash scripts/codex-cloud/setup.sh From 64a0d9399833e75efb5c44b7f09f1f7d65016c4b Mon Sep 17 00:00:00 2001 From: Hephaestus Date: Sat, 11 Jul 2026 18:10:57 +0000 Subject: [PATCH 2/3] Restore MailPlus validation posture --- .github/workflows/claude.yml | 52 ++++++-- .github/workflows/extended-validation.yml | 32 +++-- .github/workflows/pr-fast-ci.yml | 150 ++++++++++++---------- scripts/ci/run-extended-validation.sh | 14 +- scripts/ci/run-fast-checks.sh | 20 ++- 5 files changed, 173 insertions(+), 95 deletions(-) diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index e249977..3695a0c 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -4,9 +4,9 @@ on: workflow_dispatch: inputs: prompt: - description: Optional manual task prompt - required: false - type: string + description: 'Task for Claude to run in this repository' + required: true + default: 'Review the current branch changes for bugs, CI regressions, and missing tests.' issue_comment: types: [created] pull_request_review_comment: @@ -20,27 +20,55 @@ concurrency: permissions: contents: read - pull-requests: write - issues: write jobs: claude: if: | github.event_name == 'workflow_dispatch' || - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) + (github.actor == 'jmcte' && github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || + (github.actor == 'jmcte' && github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || + (github.actor == 'jmcte' && github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) runs-on: ubuntu-latest timeout-minutes: 30 + permissions: + contents: write + pull-requests: write + issues: write + id-token: write + actions: read steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 1 + persist-credentials: false + + - name: Require Claude auth + env: + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} + run: | + if [[ -z "${ANTHROPIC_API_KEY}" ]]; then + echo "Missing repository secret ANTHROPIC_API_KEY. Run /install-github-app in Claude Code or add the secret before using this workflow." >&2 + exit 1 + fi + - name: Run Claude Code - uses: anthropics/claude-code-action@v1 + uses: anthropics/claude-code-action@e90deca47693f9457b72f2b53c17d7c445a87342 # v1.0.171 with: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + track_progress: true + use_sticky_comment: true + additional_permissions: "actions: read" prompt: | REPO: ${{ github.repository }} DEFAULT BRANCH: main - REQUIRED CHECKS: CI Gate - Use CLAUDE.md, AGENTS.md, and docs/bootstrap/onboarding.md as policy context. + + Use CLAUDE.md and docs/bootstrap/onboarding.md as repo policy context. + Keep CI Gate as the single required PR status check. + Preserve the split fast and extended validation model. + Shell-safe jobs may use `[self-hosted, synology, shell-only, private]`. + Docker, service-container, browser, and `container:` jobs stay on GitHub-hosted runners. + Prefer the smallest safe change and add tests for behavior changes. + MANUAL TASK: ${{ github.event.inputs.prompt }} + If this is not a manual run, ignore the MANUAL TASK line and respond to the current `@claude` request instead. diff --git a/.github/workflows/extended-validation.yml b/.github/workflows/extended-validation.yml index 3d9774c..38c5a9a 100644 --- a/.github/workflows/extended-validation.yml +++ b/.github/workflows/extended-validation.yml @@ -25,13 +25,13 @@ defaults: jobs: changes: name: Detect Extended Validation Scope - runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] + runs-on: ['self-hosted', 'synology', 'shell-only', 'private'] outputs: app: ${{ steps.preset.outputs.app || steps.filter.outputs.app || 'false' }} ci: ${{ steps.preset.outputs.ci || steps.filter.outputs.ci || 'false' }} extended: ${{ steps.preset.outputs.extended || steps.filter.outputs.extended || 'false' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 if: github.event_name == 'push' with: fetch-depth: 0 @@ -46,7 +46,7 @@ jobs: extended=true EOF - - uses: dorny/paths-filter@v3 + - uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 id: filter if: github.event_name == 'push' with: @@ -54,10 +54,14 @@ jobs: app: - 'project.bootstrap.yaml' - 'AGENTS.md' - - 'CONTRIBUTING.md' - - '.github/PULL_REQUEST_TEMPLATE.md' + - 'CLAUDE.md' + - '.devcontainer/**' - '.githooks/**' - '.github/workflows/**' + - 'pyproject.toml' + - 'src/**' + - 'tests/**' + - 'fixtures/**' - 'scripts/**' - 'docs/bootstrap/**' - 'README.md' @@ -65,8 +69,8 @@ jobs: ci: - 'project.bootstrap.yaml' - 'AGENTS.md' - - 'CONTRIBUTING.md' - - '.github/PULL_REQUEST_TEMPLATE.md' + - 'CLAUDE.md' + - '.devcontainer/**' - '.githooks/**' - '.github/workflows/**' - 'scripts/**' @@ -79,40 +83,40 @@ jobs: fast-checks: name: Fast Checks - runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] + runs-on: ['self-hosted', 'synology', 'shell-only', 'private'] timeout-minutes: 15 needs: changes if: needs.changes.outputs.app == 'true' || needs.changes.outputs.ci == 'true' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Run fast checks run: bash scripts/ci/run-fast-checks.sh extended-checks: name: Extended Checks - runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] + runs-on: ['self-hosted', 'synology', 'shell-only', 'private'] timeout-minutes: 20 needs: changes if: needs.changes.outputs.extended == 'true' || needs.changes.outputs.app == 'true' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Run extended validation run: bash scripts/ci/run-extended-validation.sh validate-secrets: name: Validate Secrets - runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] + runs-on: ['self-hosted', 'synology', 'shell-only', 'private'] timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Scan repository for secret patterns run: bash scripts/check-detect-secrets.sh --all-files extended-validation-gate: name: Extended Validation Gate - runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] + runs-on: ['self-hosted', 'synology', 'shell-only', 'private'] if: always() needs: - changes diff --git a/.github/workflows/pr-fast-ci.yml b/.github/workflows/pr-fast-ci.yml index 2c9ce48..ce8bca2 100644 --- a/.github/workflows/pr-fast-ci.yml +++ b/.github/workflows/pr-fast-ci.yml @@ -2,7 +2,7 @@ name: PR Fast CI on: pull_request: - types: [opened, edited, synchronize, reopened, ready_for_review] + types: [opened, synchronize, reopened, ready_for_review] concurrency: group: pr-fast-${{ github.event.pull_request.number || github.ref }} @@ -12,7 +12,6 @@ permissions: contents: read env: - NODE_VERSION: '20' PYTHON_VERSION: '3.12' defaults: @@ -22,20 +21,25 @@ defaults: jobs: changes: name: Detect Relevant Changes - runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] + # Fork-controlled PR code never runs on persistent self-hosted runners. + runs-on: ubuntu-latest outputs: app: ${{ steps.filter.outputs.app }} ci: ${{ steps.filter.outputs.ci }} + packaging: ${{ steps.filter.outputs.packaging }} steps: - - uses: dorny/paths-filter@v3 + - uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 id: filter with: filters: | app: + - 'pyproject.toml' + - 'src/**' + - 'tests/**' - 'project.bootstrap.yaml' - 'AGENTS.md' - - 'CONTRIBUTING.md' - - '.github/PULL_REQUEST_TEMPLATE.md' + - 'CLAUDE.md' + - '.devcontainer/**' - '.githooks/**' - '.github/workflows/**' - 'scripts/**' @@ -43,112 +47,126 @@ jobs: - 'README.md' - 'docs/**' ci: + - 'pyproject.toml' + - 'src/**' + - 'tests/**' - 'project.bootstrap.yaml' - 'AGENTS.md' - - 'CONTRIBUTING.md' - - '.github/PULL_REQUEST_TEMPLATE.md' + - 'CLAUDE.md' + - '.devcontainer/**' - '.githooks/**' - '.github/workflows/**' - 'scripts/**' - 'docs/bootstrap/**' - '.env.example' - 'CODEOWNERS' + packaging: + - 'pyproject.toml' + - '.github/workflows/release.yml' + - 'scripts/release/**' + + actionlint: + name: Actionlint + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false + permissions: + contents: read + checks: write + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + - uses: reviewdog/action-actionlint@a5524e1c19e62881d79c1f1b9b6f09f16356e281 # v1.65.2 + with: + reporter: github-pr-check + fail_level: error + + dependency-review: + name: Dependency Review + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + - uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 + with: + fail-on-severity: moderate fast-checks: name: Fast Checks - runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] + runs-on: ubuntu-latest timeout-minutes: 15 needs: changes if: >- github.event.pull_request.draft == false && (needs.changes.outputs.app == 'true' || needs.changes.outputs.ci == 'true') steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ github.event.pull_request.head.sha }} - + persist-credentials: false + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: ${{ env.PYTHON_VERSION }} - name: Run fast checks run: bash scripts/ci/run-fast-checks.sh - validate-pr-description: - name: Validate PR Description - runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] - timeout-minutes: 5 - if: github.event.pull_request.draft == false - env: - PR_BODY: ${{ github.event.pull_request.body }} - steps: - - name: Require generated PR template content - run: | - failed=0 - - require_line() { - local line="$1" - if ! grep -Fqx "$line" <<<"$PR_BODY"; then - echo "Missing required PR section: $line" - failed=1 - fi - } - - require_line "## Summary" - require_line "## Governing Issue" - require_line "## Validation" - require_line "## Bootstrap Governance" - require_line "## Merge Automation" - require_line "## Notes" - - if grep -Eiq 'Closes #$|#|what changed|why it changed|notable tradeoffs|migration or rollout notes|follow-up work if any' <<<"$PR_BODY"; then - echo "PR body still contains template placeholder text." - failed=1 - fi - - if ! grep -Eiq '(^|[[:space:]-])(((close[sd]?|fix(e[sd])?|resolve[sd]?|refs?|part[[:space:]]+of)[[:space:]]+)?(#|[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+#|https://github\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+/issues/)[0-9]+|no issue is linked|no linked issue|without a linked issue|no governing issue)' <<<"$PR_BODY"; then - echo "PR body must close/link an issue or explicitly explain why no issue is linked." - failed=1 - fi - - if ! grep -Eiq '(^|[[:space:]-])(\[[xX]\]|not run|not applicable|n/a)' <<<"$PR_BODY"; then - echo "PR body must include validation evidence, a checked validation item, or a reason validation was not run." - failed=1 - fi - - auto_merge_evidence="$(grep -Eiv '^[[:space:]]*-[[:space:]]+\[[[:space:]]\][[:space:]]' <<<"$PR_BODY" || true)" - if ! grep -Eiq 'auto-merge (is )?(enabled|armed)|enabled auto-merge|gh pr merge --auto|auto_merge|auto merge enabled|auto-merge (is )?(unavailable|unsafe|not available|not safe)|plan-limit|fallback merge-readiness' <<<"$auto_merge_evidence"; then - echo "PR body must state that the PR author enabled auto-merge, or explain why auto-merge is unavailable/unsafe." - failed=1 - fi - - exit "$failed" - validate-secrets: name: Validate Secrets - runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] + runs-on: ubuntu-latest timeout-minutes: 10 if: github.event.pull_request.draft == false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false - name: Scan repository for secret patterns run: bash scripts/check-detect-secrets.sh --all-files + package-build: + name: Package Build + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: changes + if: >- + github.event.pull_request.draft == false && + needs.changes.outputs.packaging == 'true' + steps: + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: Build distribution artifacts + run: bash scripts/release/dry-run.sh --skip-fast-checks + ci-gate: name: CI Gate - runs-on: ['self-hosted', 'linux', 'shell-only', 'public'] + runs-on: ubuntu-latest if: always() needs: - changes + - actionlint + - dependency-review - fast-checks - - validate-pr-description - validate-secrets + - package-build steps: - name: Check required PR jobs env: RESULTS: >- changes=${{ needs.changes.result }} + actionlint=${{ needs.actionlint.result }} + dependency-review=${{ needs.dependency-review.result }} fast-checks=${{ needs.fast-checks.result }} - validate-pr-description=${{ needs.validate-pr-description.result }} validate-secrets=${{ needs.validate-secrets.result }} + package-build=${{ needs.package-build.result }} run: | failed=0 for entry in $RESULTS; do diff --git a/scripts/ci/run-extended-validation.sh b/scripts/ci/run-extended-validation.sh index fd07826..b3a15de 100755 --- a/scripts/ci/run-extended-validation.sh +++ b/scripts/ci/run-extended-validation.sh @@ -1,4 +1,16 @@ #!/usr/bin/env bash set -euo pipefail bash scripts/ci/run-fast-checks.sh -echo "No extended checks configured for the generic archetype yet." + +if command -v python3.12 >/dev/null 2>&1; then + PYTHON_BIN=python3.12 +else + PYTHON_BIN=python3 +fi + +REPORT_PATH="${TMPDIR:-/tmp}/mailplus-intelligence-evaluation.json" +PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=src "$PYTHON_BIN" scripts/evaluate.py \ + --fixtures-dir fixtures \ + --report-json "$REPORT_PATH" + +echo "Evaluation report: $REPORT_PATH" diff --git a/scripts/ci/run-fast-checks.sh b/scripts/ci/run-fast-checks.sh index b0fe54e..494cfe1 100755 --- a/scripts/ci/run-fast-checks.sh +++ b/scripts/ci/run-fast-checks.sh @@ -1,4 +1,20 @@ #!/usr/bin/env bash set -euo pipefail -echo "Generic archetype selected." -echo "Add project-specific scripts and tighten scripts/ci/run-fast-checks.sh when the stack is finalized." + +if command -v python3.12 >/dev/null 2>&1; then + PYTHON_BIN=python3.12 +else + PYTHON_BIN=python3 +fi + +"$PYTHON_BIN" - <<'PY' +import sys + +if sys.version_info < (3, 12): + raise SystemExit("Python 3.12 or newer is required") +PY + +bash scripts/check-detect-secrets.sh --all-files +bash scripts/ci/check-ci-contract.sh +PYTHONPATH=src "$PYTHON_BIN" scripts/ci/docs_smoke.py +PYTHONPATH=src "$PYTHON_BIN" -m unittest discover -s tests -v From 3f23fac249400fb8099ac4bb45282aeceae67552 Mon Sep 17 00:00:00 2001 From: Hephaestus Date: Sat, 11 Jul 2026 18:16:16 +0000 Subject: [PATCH 3/3] Restore MailPlus secret scan guard --- scripts/check-detect-secrets.sh | 204 ++++++++++++++++++++------------ 1 file changed, 130 insertions(+), 74 deletions(-) diff --git a/scripts/check-detect-secrets.sh b/scripts/check-detect-secrets.sh index f127c75..079b50e 100755 --- a/scripts/check-detect-secrets.sh +++ b/scripts/check-detect-secrets.sh @@ -1,80 +1,136 @@ - #!/usr/bin/env bash - set -euo pipefail - - mode="${1:-"--all-files"}" - ignore_globs=("scripts/check-detect-secrets.sh") - if [[ -f .detect-secrets-ignore ]]; then - while IFS= read -r ignore_glob; do - if [[ -z "$ignore_glob" ]]; then - continue - fi - if [[ "${ignore_glob:0:1}" == "#" ]]; then - continue - fi - ignore_globs+=("$ignore_glob") - done < .detect-secrets-ignore +#!/usr/bin/env bash +set -euo pipefail + +mode="${1:-"--all-files"}" +ignore_globs=("scripts/check-detect-secrets.sh" ".env.example") +if [[ -f .detect-secrets-ignore ]]; then + while IFS= read -r ignore_glob; do + if [[ -z "$ignore_glob" ]]; then + continue + fi + if [[ "${ignore_glob:0:1}" == "#" ]]; then + continue + fi + ignore_globs+=("$ignore_glob") + done < .detect-secrets-ignore +fi + +should_skip_file() { + local candidate="$1" + local ignore_glob + for ignore_glob in "${ignore_globs[@]}"; do + case "$candidate" in + $ignore_glob) + return 0 + ;; + esac + done + return 1 +} + +append_tracked_files() { + while IFS= read -r -d '' file; do + files+=("$file") + done < <(git ls-files -z) +} + +append_staged_files() { + while IFS= read -r -d '' file; do + files+=("$file") + done < <(git diff --cached --name-only --diff-filter=ACMR -z) +} + +append_untracked_files() { + while IFS= read -r -d '' file; do + files+=("$file") + done < <(git ls-files --others --exclude-standard -z) +} + +files=() +case "$mode" in + --staged) + append_staged_files + ;; + --all-files) + append_tracked_files + ;; + --all-files-with-untracked | --all-local | --local) + append_tracked_files + append_untracked_files + ;; + *) + echo "Usage: $0 [--all-files|--staged|--all-files-with-untracked|--all-local|--local]" >&2 + exit 2 + ;; +esac + +if [[ "${#files[@]}" -eq 0 ]]; then + echo "No files to scan." + exit 0 +fi + +content_patterns=( + 'ghp_' + 'github_pat_' + 'sk-live-' + 'sk-proj-' + 'AKIA[0-9A-Z]{16}' + 'BEGIN (RSA|OPENSSH|EC) PRIVATE KEY' + 'ANTHROPIC_API_KEY=' + 'OPENAI_API_KEY=' + 'SUDO_PASS=' + 'BW_SESSION=' +) + +mailplus_content_patterns=( + 'https?://[^[:space:])>"]*(oauth|authorize|token)[^[:space:])>"]*(token|code|key|secret|signature|sig|jwt|session|auth)=' + 'https?://[^[:space:])>"]*(reset|recovery|recover-password|password-reset|magic|login)[^[:space:])>"]*(token|code|key|secret|signature|sig|jwt|session|auth)=' + 'https?://[^[:space:])>"]*(pay|payment|checkout|invoice|billing)[^[:space:])>"]*(token|code|key|secret|signature|sig|jwt|session|auth)=' +) + +mailplus_path_patterns=( + '(^|/)\.env(\.|$)' + '\.eml$' + '\.mbox$' + '\.(db|sqlite|sqlite3)(-(wal|shm))?$' + '(^|/)(exports|mailplus-exports)(/|$)' + '(^|/)(mailplus|selected-text|semantic|metadata)[^/]*\.(cache|log)$' +) + +allowed_synthetic_pattern='(example\.(com|org|net|test)|\[REDACTED_[A-Z_]+\])' + +tmp_file="$(mktemp)" +trap 'rm -f "$tmp_file"' EXIT + +for file in "${files[@]}"; do + if [[ ! -f "$file" ]] || should_skip_file "$file"; then + continue + fi + printf '%s\n' "$file" >>"$tmp_file" +done + +failed=0 +while IFS= read -r file; do + for pattern in "${mailplus_path_patterns[@]}"; do + if [[ "$file" =~ $pattern ]]; then + echo "Potential MailPlus export/cache artifact '$pattern' found at $file" >&2 + failed=1 fi + done - should_skip_file() { - local candidate="$1" - local ignore_glob - for ignore_glob in "${ignore_globs[@]}"; do - case "$candidate" in - $ignore_glob) - return 0 - ;; - esac - done - return 1 - } - - files=() - if [[ "$mode" == "--staged" ]]; then - while IFS= read -r -d '' file; do - files+=("$file") - done < <(git diff --cached --name-only --diff-filter=ACMR -z) - else - while IFS= read -r -d '' file; do - files+=("$file") - done < <(git ls-files -z) + for pattern in "${content_patterns[@]}"; do + if grep -E -n "$pattern" "$file" >/dev/null 2>&1; then + echo "Potential secret pattern '$pattern' found in $file" >&2 + failed=1 fi + done - if [[ "${#files[@]}" -eq 0 ]]; then - echo "No files to scan." - exit 0 + for pattern in "${mailplus_content_patterns[@]}"; do + if grep -E -n "$pattern" "$file" | grep -Ev "$allowed_synthetic_pattern" >/dev/null 2>&1; then + echo "Potential MailPlus link leak pattern '$pattern' found in $file" >&2 + failed=1 fi + done +done <"$tmp_file" - patterns=( - 'ghp_' - 'github_pat_' - 'sk-live-' - 'sk-proj-' - 'AKIA[0-9A-Z]{16}' - 'BEGIN (RSA|OPENSSH|EC) PRIVATE KEY' - 'OPENAI_API_KEY=' - 'SUDO_PASS=' - 'BW_SESSION=' - ) - - tmp_file="$(mktemp)" - trap 'rm -f "$tmp_file"' EXIT - - for file in "${files[@]}"; do - if [[ ! -f "$file" ]] || should_skip_file "$file"; then - continue - fi - printf '%s -' "$file" >>"$tmp_file" - done - - failed=0 - while IFS= read -r file; do - for pattern in "${patterns[@]}"; do - if grep -E -n "$pattern" "$file" >/dev/null 2>&1; then - echo "Potential secret pattern '$pattern' found in $file" >&2 - failed=1 - fi - done - done <"$tmp_file" - - exit "$failed" +exit "$failed"