diff --git a/.github/workflows/sync-upstream.yml b/.github/workflows/sync-upstream.yml index 4c07fed..08625cd 100644 --- a/.github/workflows/sync-upstream.yml +++ b/.github/workflows/sync-upstream.yml @@ -1,12 +1,15 @@ +# Generated by labkit (python -m labkit gen). Do not edit by hand. +# Edit labs//publish.config.json and regenerate; drift fails `labkit doctor`. +# Install into operatorstack/value-map at .github/workflows/sync-upstream.yml (bootstrap step). name: Sync from Intelligence Flow on: schedule: - - cron: "23 */6 * * *" + - cron: "9 */6 * * *" workflow_dispatch: inputs: source_commit: - description: Exact Intelligence Flow commit dispatched by Operator Stack Publisher + description: Exact Intelligence Flow commit to project (defaults to main) required: false type: string @@ -23,69 +26,96 @@ jobs: runs-on: ubuntu-latest steps: - name: Create Operator Stack Publisher token - id: publisher-token + id: app-token uses: actions/create-github-app-token@v3 with: client-id: ${{ vars.OPERATOR_STACK_PUBLISHER_APP_CLIENT_ID || vars.BOATSTACK_APP_CLIENT_ID }} private-key: ${{ secrets.OPERATOR_STACK_PUBLISHER_APP_PRIVATE_KEY || secrets.BOATSTACK_APP_PRIVATE_KEY }} owner: operatorstack - repositories: value-map - permission-contents: read + repositories: | + intelligence-flow + value-map + permission-contents: write permission-pull-requests: write - name: Check out Value Map uses: actions/checkout@v4 with: - path: value-map-repo + path: public-repo + token: ${{ steps.app-token.outputs.token }} - name: Check out Intelligence Flow uses: actions/checkout@v4 with: repository: operatorstack/intelligence-flow ref: ${{ inputs.source_commit || 'main' }} - path: intelligence-flow fetch-depth: 0 - - name: Project canonical skill - id: project + path: intelligence-flow + token: ${{ steps.app-token.outputs.token }} + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install labkit + shell: bash + run: python3 -m pip install --quiet ./intelligence-flow/labkit + - name: Generate projection + id: generate shell: bash run: | - if [[ -n "${{ inputs.source_commit }}" ]]; then - source_commit="$(git -C intelligence-flow rev-parse HEAD)" - else - source_commit="$(git -C intelligence-flow log -1 --format=%H -- labs/14-product-value-projection)" + source_commit="$(git -C intelligence-flow log -1 --format=%H -- labs/14-product-value-projection)" + current_commit="$(jq -r '.source.commit // empty' public-repo/UPSTREAM.json 2>/dev/null || echo '')" + if [[ -n "$current_commit" ]] && + ! git -C intelligence-flow merge-base --is-ancestor "$current_commit" "$source_commit"; then + echo "Ignoring stale request; Value Map already records $current_commit." + echo "stale=true" >> "$GITHUB_OUTPUT" + exit 0 fi - python3 value-map-repo/scripts/project_upstream.py \ - --source intelligence-flow \ - --repo value-map-repo \ - --commit "$source_commit" \ + python3 -m labkit project \ + --config intelligence-flow/labs/14-product-value-projection/publish.config.json \ + --repo public-repo \ + --source-commit "$source_commit" \ --write echo "source_commit=$source_commit" >> "$GITHUB_OUTPUT" + echo "stale=false" >> "$GITHUB_OUTPUT" - name: Open generated pull request + if: steps.generate.outputs.stale != 'true' env: - GH_TOKEN: ${{ steps.publisher-token.outputs.token }} - SOURCE_COMMIT: ${{ steps.project.outputs.source_commit }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + SOURCE_COMMIT: ${{ steps.generate.outputs.source_commit }} shell: bash run: | - cd value-map-repo + cd public-repo if [[ -z "$(git status --porcelain)" ]]; then echo "Value Map already matches Intelligence Flow." exit 0 fi + git add -A + body_file="$(mktemp)" + { + echo "## Projection provenance"; echo + echo "Generated from \`operatorstack/intelligence-flow@$SOURCE_COMMIT\`." + echo "Review the operator contract, provenance, and compatibility before merging." + } > "$body_file" short="${SOURCE_COMMIT:0:12}" branch="sync/intelligence-flow-$short" existing="$(gh pr list --head "$branch" --state open --json url --jq '.[0].url')" - if [[ -n "$existing" ]]; then - echo "Upstream PR already open: $existing" - gh pr merge "$existing" --auto --squash - exit 0 - fi - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git checkout -B "$branch" - git add value-map/SKILL.md UPSTREAM.json - git commit -m "Sync Value Map from Intelligence Flow $short" + git config user.name "${{ steps.app-token.outputs.app-slug }}[bot]" + git config user.email "${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com" + git switch -c "$branch" + git commit -m "Sync Value Map from Intelligence Flow @ $short" git push --force --set-upstream origin "$branch" - pr_url="$(gh pr create \ - --base main \ - --head "$branch" \ - --title "Sync Value Map from Intelligence Flow $short" \ - --body "Generated from operatorstack/intelligence-flow@$SOURCE_COMMIT. Review the operator contract, provenance, and compatibility checks before merging.")" + if [[ -z "$existing" ]]; then + pr_url="$(gh pr create --base main --head "$branch" \ + --title "Sync Value Map from Intelligence Flow @ $short" \ + --body-file "$body_file")" + echo "Opened generated PR: $pr_url" + else + pr_url="$existing" + echo "Updated existing PR: $existing" + fi + required_checks="$(gh api "repos/operatorstack/value-map/branches/main/protection/required_status_checks" --jq '(.contexts // []) + ([.checks[]?.context] // []) | unique | length')" + if [[ "$required_checks" -lt 1 ]]; then + echo "BLOCKED: main has no required status checks; refusing unsafe auto-merge." >&2 + exit 1 + fi gh pr merge "$pr_url" --auto --squash + echo "Native auto-merge requested; branch protection owns merge eligibility." diff --git a/tests/test_distribution.py b/tests/test_distribution.py index ee4831b..ddcc985 100644 --- a/tests/test_distribution.py +++ b/tests/test_distribution.py @@ -101,9 +101,12 @@ def test_sync_accepts_exact_publisher_dispatch_with_polling_fallback(self) -> No self.assertIn("source_commit:", workflow) self.assertIn("Operator Stack Publisher", workflow) self.assertIn("inputs.source_commit || 'main'", workflow) - self.assertIn('cron: "23 */6 * * *"', workflow) + self.assertIn('cron: "9 */6 * * *"', workflow) self.assertIn("actions/create-github-app-token@v3", workflow) - self.assertIn("steps.publisher-token.outputs.token", workflow) + self.assertIn("steps.app-token.outputs.token", workflow) + self.assertIn("permission-contents: write", workflow) + self.assertIn('gh pr merge "$pr_url" --auto --squash', workflow) + self.assertIn("required_status_checks", workflow) self.assertNotIn("GH_TOKEN: ${{ github.token }}", workflow)