From 7b8745b175a8ed844a5aa25b3a78778412ea22af Mon Sep 17 00:00:00 2001 From: Marcus Burghardt Date: Thu, 7 May 2026 17:55:21 +0200 Subject: [PATCH 1/2] fix: restore peribolos with reliable auth and drift detection - Replace expired APP_ACCESS_TOKEN with per-run GitHub App installation tokens via actions/create-github-app-token (complytime-bot app) - Fix silent failure masking by adding set -o pipefail to the pipeline - Add workflow_dispatch trigger with dry-run option for on-demand reapply - Add daily scheduled reconciliation (cron 05:30 UTC) - Add weekly drift detection workflow that opens issues on org state drift - Remove unused ghproxy sidecar from apply workflow - Fix org admins listed as team members instead of maintainers - Remove orphan docs: key from peribolos.yaml - Wire dead testTeamMembers() into TestOrgs() for team config validation - Use process substitution for token passing (no credentials on disk) Assisted-by: OpenCode (claude-opus-4-6) Signed-off-by: Marcus Burghardt --- .github/workflows/apply_peribolos.yml | 90 ++++++++++--- .github/workflows/drift_detection.yml | 126 ++++++++++++++++++ .gitignore | 23 ++++ config/config_test.go | 6 + .../.openspec.yaml | 2 + .../fix-peribolos-implementation/design.md | 95 +++++++++++++ .../fix-peribolos-implementation/proposal.md | 41 ++++++ .../specs/config-hygiene/spec.md | 30 +++++ .../specs/drift-detection/spec.md | 37 +++++ .../specs/manual-reapply/spec.md | 24 ++++ .../specs/scheduled-reconciliation/spec.md | 26 ++++ .../specs/token-auth/spec.md | 78 +++++++++++ .../fix-peribolos-implementation/tasks.md | 37 +++++ openspec/schemas/unbound-force/schema.yaml | 77 +++++++++++ .../schemas/unbound-force/templates/design.md | 20 +++ .../unbound-force/templates/proposal.md | 57 ++++++++ .../schemas/unbound-force/templates/spec.md | 23 ++++ .../schemas/unbound-force/templates/tasks.md | 22 +++ peribolos.yaml | 15 ++- 19 files changed, 800 insertions(+), 29 deletions(-) create mode 100644 .github/workflows/drift_detection.yml create mode 100644 openspec/changes/fix-peribolos-implementation/.openspec.yaml create mode 100644 openspec/changes/fix-peribolos-implementation/design.md create mode 100644 openspec/changes/fix-peribolos-implementation/proposal.md create mode 100644 openspec/changes/fix-peribolos-implementation/specs/config-hygiene/spec.md create mode 100644 openspec/changes/fix-peribolos-implementation/specs/drift-detection/spec.md create mode 100644 openspec/changes/fix-peribolos-implementation/specs/manual-reapply/spec.md create mode 100644 openspec/changes/fix-peribolos-implementation/specs/scheduled-reconciliation/spec.md create mode 100644 openspec/changes/fix-peribolos-implementation/specs/token-auth/spec.md create mode 100644 openspec/changes/fix-peribolos-implementation/tasks.md create mode 100644 openspec/schemas/unbound-force/schema.yaml create mode 100644 openspec/schemas/unbound-force/templates/design.md create mode 100644 openspec/schemas/unbound-force/templates/proposal.md create mode 100644 openspec/schemas/unbound-force/templates/spec.md create mode 100644 openspec/schemas/unbound-force/templates/tasks.md diff --git a/.github/workflows/apply_peribolos.yml b/.github/workflows/apply_peribolos.yml index 0557396..1ccf0d6 100644 --- a/.github/workflows/apply_peribolos.yml +++ b/.github/workflows/apply_peribolos.yml @@ -1,14 +1,35 @@ +# Workflow to apply the Peribolos org configuration. +# Runs on push to main, daily schedule, or manual dispatch. +# Uses the complytime-bot GitHub App for authentication. name: Apply Peribolos + on: push: branches: - main pull_request: + schedule: + # Daily at 05:30 UTC — results visible by EU morning (07:30 CEST / 06:30 CET) + - cron: '30 5 * * *' + workflow_dispatch: + inputs: + dry-run: + description: 'Run Peribolos without --confirm (dry-run mode)' + required: false + type: boolean + default: false + +concurrency: + group: peribolos-apply + cancel-in-progress: false + jobs: Apply-peribolos: runs-on: ubuntu-latest timeout-minutes: 20 - steps: + permissions: + contents: read + steps: - name: Checkout complytime/.github repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -19,36 +40,61 @@ jobs: - name: Copy peribolos.yaml run: cp peribolos.yaml /tmp - - - name: Checkout ghproxy and peribolos code - if: ${{ github.repository_owner == 'complytime' && github.event_name == 'push' && github.ref == 'refs/heads/main' }} + + - name: Checkout peribolos code + if: >- + github.repository_owner == 'complytime' && + github.event_name != 'pull_request' uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: repository: kubernetes-sigs/prow - - name: Build ghproxy - if: ${{ github.repository_owner == 'complytime' && github.event_name == 'push' && github.ref == 'refs/heads/main' }} - run: | - cd cmd/ghproxy - go mod tidy - go build -o ghproxy . - cp ghproxy /tmp - - name: Build peribolos - if: ${{ github.repository_owner == 'complytime' && github.event_name == 'push' && github.ref == 'refs/heads/main' }} + if: >- + github.repository_owner == 'complytime' && + github.event_name != 'pull_request' run: | cd cmd/peribolos go mod tidy - go build -o . + go build -o . cp peribolos /tmp + - name: Generate GitHub App token + if: >- + github.repository_owner == 'complytime' && + github.event_name != 'pull_request' + id: app-token + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + with: + client-id: ${{ secrets.COMPLYTIME_BOT_CLIENT_ID }} + private-key: ${{ secrets.COMPLYTIME_BOT_PRIVATE_KEY }} + owner: complytime + - name: Apply peribolos.yaml - if: ${{ github.repository_owner == 'complytime' && github.event_name == 'push' && github.ref == 'refs/heads/main' }} + if: >- + github.repository_owner == 'complytime' && + github.event_name != 'pull_request' + env: + APP_TOKEN: ${{ steps.app-token.outputs.token }} + DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && inputs.dry-run }} run: | - echo ${{ secrets.APP_ACCESS_TOKEN }} > auth.txt - /tmp/ghproxy --legacy-disable-disk-cache-partitions-by-auth-header=false --get-throttling-time-ms=300 --throttling-time-ms=900 --throttling-time-v4-ms=850 --throttling-max-delay-duration-seconds=45 --throttling-max-delay-duration-v4-seconds=110 --request-timeout=120 1>/dev/null 2>&1 & - pid=$! - jobs - /tmp/peribolos --config-path /tmp/peribolos.yaml --fix-org --fix-org-members --fix-teams --fix-team-members --fix-repos --fix-team-repos --min-admins 2 --github-token-path auth.txt --confirm 2>&1 | jq -r '[.severity, .time, .msg] | join(" | ")' - kill $pid - rm auth.txt + set -o pipefail + + PERIBOLOS_ARGS=( + --config-path /tmp/peribolos.yaml + --fix-org + --fix-org-members + --fix-teams + --fix-team-members + --fix-repos + --fix-team-repos + --min-admins 2 + --require-self=false + --github-token-path <(printf '%s' "$APP_TOKEN") + ) + + if [ "$DRY_RUN" != "true" ]; then + PERIBOLOS_ARGS+=(--confirm) + fi + + /tmp/peribolos "${PERIBOLOS_ARGS[@]}" 2>&1 | jq -r '[.severity, .time, .msg] | join(" | ")' diff --git a/.github/workflows/drift_detection.yml b/.github/workflows/drift_detection.yml new file mode 100644 index 0000000..6888edd --- /dev/null +++ b/.github/workflows/drift_detection.yml @@ -0,0 +1,126 @@ +# Workflow to detect drift between peribolos.yaml and actual GitHub org state. +# Runs weekly on Monday mornings. Opens or updates a GitHub issue when drift is detected. +name: Drift Detection + +on: + schedule: + # Monday at 04:30 UTC — drift issues visible by EU morning (06:30 CEST / 05:30 CET), + # before daily reconciliation at 05:30 UTC + - cron: '30 4 * * 1' + workflow_dispatch: + +jobs: + detect-drift: + if: github.repository_owner == 'complytime' + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read + issues: write + steps: + - name: Checkout complytime/.github repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version-file: './go.mod' + + - name: Checkout and build peribolos + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + repository: kubernetes-sigs/prow + + - name: Build peribolos + run: | + cd cmd/peribolos + go mod tidy + go build -o . + cp peribolos /tmp + + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1 + with: + client-id: ${{ secrets.COMPLYTIME_BOT_CLIENT_ID }} + private-key: ${{ secrets.COMPLYTIME_BOT_PRIVATE_KEY }} + owner: complytime + + - name: Dump current org state + env: + APP_TOKEN: ${{ steps.app-token.outputs.token }} + run: | + /tmp/peribolos \ + --config-path peribolos.yaml \ + --require-self=false \ + --github-token-path <(printf '%s' "$APP_TOKEN") \ + --dump complytime \ + --dump-full 2>/dev/null | yq -P 'sort_keys(..)' > /tmp/org-actual.yaml + yq -P 'sort_keys(..)' peribolos.yaml > /tmp/org-expected.yaml + + - name: Compare org state + id: diff + run: | + if diff -u /tmp/org-expected.yaml /tmp/org-actual.yaml > /tmp/drift-diff.txt 2>&1; then + echo "drift=false" >> "$GITHUB_OUTPUT" + echo "No drift detected." + else + echo "drift=true" >> "$GITHUB_OUTPUT" + echo "Drift detected between peribolos.yaml and actual org state." + fi + + - name: Check for existing drift issue + if: steps.diff.outputs.drift == 'true' + id: existing-issue + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + ISSUE_NUMBER=$(gh issue list \ + --label peribolos-drift \ + --state open \ + --limit 1 \ + --json number \ + --jq '.[0].number // empty') + echo "issue_number=${ISSUE_NUMBER}" >> "$GITHUB_OUTPUT" + + - name: Create or update drift issue + if: steps.diff.outputs.drift == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ steps.existing-issue.outputs.issue_number }} + WORKFLOW_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + run: | + TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) + + { + echo "## Peribolos Drift Detected" + echo "" + echo "**Date**: ${TIMESTAMP}" + echo "**Workflow run**: ${WORKFLOW_URL}" + echo "" + echo "The actual GitHub org state differs from what is declared in \`peribolos.yaml\`." + echo "This may indicate manual changes were made via the GitHub UI." + echo "" + echo "### Diff" + echo "" + echo '```diff' + cat /tmp/drift-diff.txt + echo '```' + echo "" + echo "### Recommended Action" + echo "" + echo "- Review the diff to determine if the changes are intentional" + echo "- If unintentional: trigger a manual Peribolos apply via \`workflow_dispatch\`" + echo "- If intentional: update \`peribolos.yaml\` to match the desired state" + } > /tmp/issue-body.md + + if [ -n "$ISSUE_NUMBER" ]; then + gh issue edit "$ISSUE_NUMBER" --body-file /tmp/issue-body.md + echo "Updated existing issue #${ISSUE_NUMBER}" + else + gh issue create \ + --title "Peribolos Drift Detected - $(date -u +%Y-%m-%d)" \ + --body-file /tmp/issue-body.md \ + --label peribolos-drift + echo "Created new drift issue" + fi diff --git a/.gitignore b/.gitignore index 77f9775..7541590 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,26 @@ go.work.sum # Editor/IDE .idea/ .vscode/ + +# Unbound Force — managed by uf init +# Runtime data under .uf/ (databases, caches, locks, logs) +.uf/workflows/ +.uf/artifacts/ +.uf/dewey/graph.db +.uf/dewey/graph.db-shm +.uf/dewey/graph.db-wal +.uf/dewey/*.lock +.uf/dewey/cache/ +.uf/dewey/dewey.log +.uf/replicator/*.db +.uf/replicator/*.db-shm +.uf/replicator/*.db-wal +.uf/replicator/*.lock +.uf/muti-mind/artifacts/ +.uf/mx-f/data/ +# Legacy tool directories (renamed to .uf/ in Spec 025) +.dewey/ +.hive/ +.unbound-force/ +.muti-mind/ +.mx-f/ diff --git a/config/config_test.go b/config/config_test.go index 765cb89..a0e4c82 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -225,5 +225,11 @@ func TestOrgs(t *testing.T) { if !isSorted(org.Members) { t.Errorf("members for %s org are unsorted", *org.Name) } + + if org.Teams != nil { + for _, err := range testTeamMembers(org.Teams, admins, allOrgMembers, *org.Name) { + t.Errorf("%v", err) + } + } } } diff --git a/openspec/changes/fix-peribolos-implementation/.openspec.yaml b/openspec/changes/fix-peribolos-implementation/.openspec.yaml new file mode 100644 index 0000000..8d87be1 --- /dev/null +++ b/openspec/changes/fix-peribolos-implementation/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-07 diff --git a/openspec/changes/fix-peribolos-implementation/design.md b/openspec/changes/fix-peribolos-implementation/design.md new file mode 100644 index 0000000..a965114 --- /dev/null +++ b/openspec/changes/fix-peribolos-implementation/design.md @@ -0,0 +1,95 @@ +## Context + +The `complytime` GitHub organization uses Peribolos (a Prow CLI tool) to manage org settings, teams, memberships, and repository permissions as code via `peribolos.yaml`. The implementation lives in the `complytime/.github` repository. + +Current state: +- Peribolos has been silently failing since April 16, 2026 due to an expired GitHub App user access token +- The workflow pipeline masks failures: `peribolos ... 2>&1 | jq ...` swallows the exit code +- Every run since April 16 reports `success` while Peribolos exits with `fatal: Configuration failed: status code 404` +- The `complytime-bot` GitHub App is installed with correct permissions (`organization_administration: write`, `members: write`, `administration: write`) +- `pme-bot` is a regular user account (not an app bot) whose expired token was stored in `secrets.APP_ACCESS_TOKEN` +- The `testTeamMembers()` function in `config/config_test.go` is defined but never called from any test +- Org admins (`jpower432`, `marcusburghardt`) are listed as `members:` instead of `maintainers:` in several teams + +## Goals / Non-Goals + +### Goals + +- Restore Peribolos to a working state with reliable, self-renewing authentication +- Make Peribolos failures visible (fail the workflow when Peribolos fails) +- Enable on-demand manual reapplication of org settings +- Enable daily automatic reconciliation +- Detect and alert when org state drifts from the declared config +- Fix config and test issues that allow invalid configurations + +### Non-Goals + +- Managing branch protection rules (requires `branchprotector`, a separate Prow tool) +- Managing GitHub Actions permissions, webhooks, or repository rulesets +- Adding new repository settings beyond what is currently declared (keep it minimalist per user preference) +- Migrating away from Peribolos to a different tool + +## Decisions + +### D1: Use `actions/create-github-app-token` for authentication + +**Decision**: Replace the static `APP_ACCESS_TOKEN` secret with per-run installation tokens generated by `actions/create-github-app-token@v3` (SHA-pinned per existing workflow conventions). + +**Rationale**: The existing `complytime-bot` GitHub App already has all required permissions. Installation tokens are generated fresh per-run (1-hour TTL, auto-revoked after job), eliminating token expiry as a failure mode. This is the GitHub-recommended approach (official action, 800+ stars). The user access token approach requires manual regeneration every 8 hours and the device flow requires human interaction, making it unsuitable for CI. + +**Alternative considered**: Fine-grained PAT — simpler but still requires manual rotation. Refresh token rotation in CI is fragile and creates security risks (workflow modifying its own secrets). + +**Configuration**: +- `secrets.COMPLYTIME_BOT_CLIENT_ID` and `secrets.COMPLYTIME_BOT_PRIVATE_KEY` (already created) +- Token scoped to `owner: complytime` for org-wide access +- `skip-token-revoke: false` (default, auto-revoke after job) + +**Migration plan**: +1. Deploy the updated workflow while `APP_ACCESS_TOKEN` still exists as a fallback reference +2. Run a manual `workflow_dispatch` dry-run to validate token generation works +3. After one successful push-triggered run on `main`, mark `APP_ACCESS_TOKEN` as deprecated +4. Remove `APP_ACCESS_TOKEN` after 3 successful runs + +### D2: Add `--require-self=false` to Peribolos + +**Decision**: Disable the `--require-self` check (which defaults to `true`). + +**Rationale**: The `--require-self` flag calls `GET /user` to verify the authenticated user is an org admin. Installation tokens cannot call `GET /user` (it's a user-only endpoint). This is the only Peribolos endpoint incompatible with installation tokens. The safety check is replaced by `--min-admins 2` and the app's own permission constraints (the App can only perform actions within its granted permission scopes). + +### D3: Fix pipeline exit code propagation + +**Decision**: Add `set -o pipefail` to the shell step that runs Peribolos. + +**Rationale**: Without `pipefail`, the exit code of `peribolos ... | jq ...` is jq's exit code (always 0), not Peribolos' exit code. This is the root cause of silent failures since April 16. + +### D4: Remove ghproxy from the apply workflow + +**Decision**: Remove the ghproxy sidecar process from the workflow. + +**Rationale**: Peribolos is not configured to route through ghproxy (no `--github-endpoint=http://localhost:8888` flag), so ghproxy runs but is never used. The warning `"It doesn't look like you are using ghproxy"` confirms this. For a small org (~12 repos, ~20 members, 5 teams), API rate limiting is not a concern. Removing it simplifies the workflow. + +### D5: Drift detection via `peribolos --dump` + +**Decision**: Create a separate weekly scheduled workflow that runs `peribolos --dump complytime` to capture actual org state, then diffs against `peribolos.yaml`. Opens a GitHub issue when drift is detected. + +**Rationale**: Even with daily reconciliation, there is value in explicitly detecting drift. Weekly frequency is chosen because daily reconciliation handles remediation; drift detection catches persistent or reconciliation-resistant drift. A separate workflow keeps concerns isolated from the apply workflow. + +### D6: Trigger behavior matrix + +The apply workflow supports multiple trigger types with different behaviors: + +| Trigger | Token Gen | Peribolos Build | Apply (`--confirm`) | Dry-run only | +|---|---|---|---|---| +| `pull_request` | No | No | No | No (skip entirely) | +| `push` to `main` | Yes | Yes | Yes | No | +| `workflow_dispatch` (dry-run=false) | Yes | Yes | Yes | No | +| `workflow_dispatch` (dry-run=true) | Yes | Yes | No | Yes | +| `schedule` (daily cron) | Yes | Yes | Yes | No | + +## Risks / Trade-offs + +- **[`--require-self=false` removes a safety check]** → Mitigated by `--min-admins 2` flag, which prevents accidental admin removal. The app's installation permissions constrain what can be changed. +- **[Installation token 1-hour TTL]** → Peribolos runs complete in ~2 minutes for this org size. No risk of timeout. +- **[Drift detection may be noisy]** → The detection workflow only opens an issue, it does not auto-remediate. Org admins can triage and decide whether to reapply or update config. +- **[Removing ghproxy]** → If the org grows significantly, rate limiting could become relevant. ghproxy can be re-added with proper `--github-endpoint` configuration if needed. +- **[Upstream Prow dependency]** → The workflow builds Peribolos from source via `kubernetes-sigs/prow`. If the repo is unavailable or the build breaks, all runs fail. Go module caching mitigates transient outages. diff --git a/openspec/changes/fix-peribolos-implementation/proposal.md b/openspec/changes/fix-peribolos-implementation/proposal.md new file mode 100644 index 0000000..fb74be0 --- /dev/null +++ b/openspec/changes/fix-peribolos-implementation/proposal.md @@ -0,0 +1,41 @@ +## Why + +Peribolos has been silently failing on every run since April 16, 2026. The GitHub App user access token expired and the workflow's pipeline exit code masking (piping through `jq`) hides the failure, making every run appear successful. As a result, team-repo permissions, org membership changes, and other org settings declared in `peribolos.yaml` are never applied. Org maintainers resort to manual changes, undermining the entire org-as-code approach. + +## What Changes + +- **Fix authentication**: Replace the expired static `APP_ACCESS_TOKEN` with per-run installation tokens generated via `actions/create-github-app-token@v3`, using the existing `complytime-bot` GitHub App. This eliminates token expiry as a failure mode entirely. +- **Fix silent failure masking**: Add `set -o pipefail` to the apply step so Peribolos failures propagate as workflow failures instead of being swallowed by the `jq` pipeline. +- **Add manual reapply trigger**: Add `workflow_dispatch` to `apply_peribolos.yml` so org admins can reapply settings on demand. +- **Add daily scheduled reconciliation**: Add a cron schedule to run Peribolos daily, preventing drift accumulation between config changes. +- **Fix dead test code**: Wire the existing `testTeamMembers()` function into `TestOrgs()` in `config/config_test.go` — it is defined but never called, so team config validation never runs. +- **Fix org admins listed as team members**: Move `jpower432` and `marcusburghardt` from `members:` to `maintainers:` in teams where they appear (they are org admins and GitHub treats them differently). +- **Remove orphan config key**: Remove the unrecognized `docs:` key from `peribolos.yaml`. +- **Add weekly drift detection workflow**: Create a weekly scheduled workflow that detects org state drift and opens an issue when config and reality diverge. Weekly frequency is chosen because daily reconciliation handles remediation; this workflow is advisory. + +## Capabilities + +### New Capabilities + +- `token-auth`: Replace static token authentication with per-run GitHub App installation token generation via `actions/create-github-app-token@v3` +- `manual-reapply`: Add `workflow_dispatch` trigger with optional dry-run input so org admins can reapply Peribolos on demand +- `scheduled-reconciliation`: Add daily cron schedule to the apply workflow for continuous reconciliation +- `drift-detection`: New weekly scheduled workflow that compares actual org state against `peribolos.yaml` and opens an issue on drift +- `config-hygiene`: Fix peribolos.yaml config errors and activate dead test code in `config/config_test.go` + +### Modified Capabilities + + + +### Removed Capabilities + +- `ghproxy-sidecar`: Remove the unused ghproxy background process from the apply workflow (Peribolos was never configured to route through it) +- `app-access-token`: Replace static `APP_ACCESS_TOKEN` secret with per-run installation tokens (old secret can be removed after migration) + +## Impact + +- **Workflows**: `apply_peribolos.yml` (authentication, triggers, error handling), new `drift_detection.yml` +- **Config**: `peribolos.yaml` (fix `docs:` key, fix admin/member placement in teams) +- **Tests**: `config/config_test.go` (activate dead test code) +- **Secrets**: New `COMPLYTIME_BOT_CLIENT_ID` and `COMPLYTIME_BOT_PRIVATE_KEY` secrets replace old `APP_ACCESS_TOKEN` (manual setup, already done) +- **Dependencies**: New workflow dependency on `actions/create-github-app-token@v3` (SHA-pinned per existing workflow conventions) diff --git a/openspec/changes/fix-peribolos-implementation/specs/config-hygiene/spec.md b/openspec/changes/fix-peribolos-implementation/specs/config-hygiene/spec.md new file mode 100644 index 0000000..52df4a0 --- /dev/null +++ b/openspec/changes/fix-peribolos-implementation/specs/config-hygiene/spec.md @@ -0,0 +1,30 @@ +## ADDED Requirements + +### Requirement: No unrecognized keys in peribolos.yaml +The `peribolos.yaml` configuration SHALL NOT contain keys that are not recognized by the Peribolos org config schema. Unrecognized keys are silently ignored and indicate config errors. + +#### Scenario: Orphan docs key is removed +- **GIVEN** `peribolos.yaml` currently contains a `docs:` key at the org level that is not a recognized Peribolos config field +- **WHEN** the config is corrected +- **THEN** the `docs:` key and its contents are removed from `peribolos.yaml` + +### Requirement: Org admins listed as team maintainers not members +Org admins SHALL NOT appear in any team's `members:` list. Org admins who are part of a team MUST be listed under `maintainers:` instead, because GitHub automatically grants admin-level access to org owners and Peribolos validates this distinction. + +#### Scenario: Org admin not in team members +- **GIVEN** `jpower432` and `marcusburghardt` are org admins (listed in `admins:`) +- **WHEN** `testTeamMembers()` checks team membership +- **THEN** neither admin appears in any team's `members:` list (they are in `maintainers:` instead) + +### Requirement: testTeamMembers validation is active +The `testTeamMembers()` function in `config/config_test.go` SHALL be called from `TestOrgs()` for every org's teams. This function validates team privacy, prevents admins from being listed as regular members, checks for duplicates, and verifies sorted member lists. + +#### Scenario: Dead test code is wired into test suite +- **GIVEN** `testTeamMembers()` exists in `config/config_test.go` but is currently not called +- **WHEN** `TestOrgs()` runs +- **THEN** `testTeamMembers()` is invoked for each org's teams and its validations are enforced + +#### Scenario: Tests pass with corrected config +- **GIVEN** org admins have been moved from `members:` to `maintainers:` and the orphan `docs:` key has been removed +- **WHEN** `go test ./config/...` is executed +- **THEN** all tests pass including the newly-wired `testTeamMembers()` validations diff --git a/openspec/changes/fix-peribolos-implementation/specs/drift-detection/spec.md b/openspec/changes/fix-peribolos-implementation/specs/drift-detection/spec.md new file mode 100644 index 0000000..bda99c5 --- /dev/null +++ b/openspec/changes/fix-peribolos-implementation/specs/drift-detection/spec.md @@ -0,0 +1,37 @@ +## ADDED Requirements + +**Depends on**: `token-auth` (for `peribolos --dump` authentication pattern) + +### Requirement: Scheduled drift detection workflow +A new workflow SHALL run on a weekly schedule (e.g., `cron: '30 5 * * 1'` — Monday before the daily reconciliation), dump the current org state using `peribolos --dump`, and compare it against `peribolos.yaml`. When differences are detected, it SHALL open a GitHub issue. The workflow SHALL declare explicit `permissions:` with `contents: read` and `issues: write`. + +#### Scenario: No drift detected +- **GIVEN** the `complytime-bot` app is installed and the org state matches `peribolos.yaml` +- **WHEN** the drift detection workflow runs +- **THEN** no issue is created and the workflow succeeds + +#### Scenario: Drift detected +- **GIVEN** the org state differs from `peribolos.yaml` (e.g., a team-repo permission was changed via the GitHub UI) +- **WHEN** the drift detection workflow runs +- **THEN** a new GitHub issue is opened with the label `peribolos-drift`, a title like `Peribolos Drift Detected - `, and a body containing the diff output wrapped in a fenced code block + +#### Scenario: Existing open drift issue +- **GIVEN** an open issue with the `peribolos-drift` label already exists +- **WHEN** the drift detection workflow detects new drift +- **THEN** the workflow updates the existing issue body with the new diff instead of creating a duplicate + +### Requirement: Drift detection uses separate authentication scopes +Issue creation/update SHALL use the workflow's default `GITHUB_TOKEN` with `issues: write` permission. The GitHub App installation token SHALL only be used for `peribolos --dump` (org read operations). + +#### Scenario: Token scoping is correct +- **GIVEN** the drift detection workflow generates an installation token for `peribolos --dump` +- **WHEN** an issue needs to be created or updated +- **THEN** the `GITHUB_TOKEN` is used for issue operations, not the installation token + +### Requirement: Diff output is sanitized for safe display +The diff output included in the issue body SHALL be wrapped in a fenced code block (triple backticks) to prevent Markdown injection (e.g., `@mentions`, `#references`). The diff SHALL be passed to the issue creation API via a file or environment variable, not inline shell expansion. + +#### Scenario: Diff content is safely rendered +- **GIVEN** the org state contains usernames or metadata with special characters +- **WHEN** the diff is included in the GitHub issue body +- **THEN** the content is rendered as a code block without triggering notifications or rendering as active Markdown diff --git a/openspec/changes/fix-peribolos-implementation/specs/manual-reapply/spec.md b/openspec/changes/fix-peribolos-implementation/specs/manual-reapply/spec.md new file mode 100644 index 0000000..0e2a1a8 --- /dev/null +++ b/openspec/changes/fix-peribolos-implementation/specs/manual-reapply/spec.md @@ -0,0 +1,24 @@ +## ADDED Requirements + +**Depends on**: `token-auth` (authentication mechanism) + +### Requirement: Workflow dispatch trigger for on-demand reapply +The apply workflow SHALL include a `workflow_dispatch` trigger so users with write access to the repository can manually reapply Peribolos settings at any time. Authorization relies on GitHub's built-in access control for `workflow_dispatch`. + +#### Scenario: Admin triggers manual reapply +- **GIVEN** the apply workflow is deployed with `workflow_dispatch` trigger and the user has write access to the repository +- **WHEN** the user navigates to the workflow in GitHub Actions and clicks "Run workflow" +- **THEN** the workflow generates a fresh installation token (per `token-auth` spec), runs Peribolos with `--confirm`, and applies all settings from `peribolos.yaml` + +### Requirement: Dry-run option for manual dispatch +The `workflow_dispatch` trigger SHALL accept a boolean input named `dry-run` (default: `false`). When `true`, Peribolos SHALL run without the `--confirm` flag, showing what would change without making mutations. + +#### Scenario: Admin triggers dry-run +- **GIVEN** the apply workflow is deployed with the `dry-run` input +- **WHEN** a user triggers the workflow with `dry-run` set to `true` +- **THEN** Peribolos runs without `--confirm` and logs what changes would be made without applying them + +#### Scenario: Admin triggers normal apply +- **GIVEN** the apply workflow is deployed with the `dry-run` input +- **WHEN** a user triggers the workflow with `dry-run` set to `false` (or default) +- **THEN** Peribolos runs with `--confirm` and applies all changes diff --git a/openspec/changes/fix-peribolos-implementation/specs/scheduled-reconciliation/spec.md b/openspec/changes/fix-peribolos-implementation/specs/scheduled-reconciliation/spec.md new file mode 100644 index 0000000..494fdba --- /dev/null +++ b/openspec/changes/fix-peribolos-implementation/specs/scheduled-reconciliation/spec.md @@ -0,0 +1,26 @@ +## ADDED Requirements + +**Depends on**: `token-auth` (authentication mechanism) + +### Requirement: Daily scheduled Peribolos execution +The apply workflow SHALL include a `schedule` trigger with a daily cron expression (e.g., `cron: '30 6 * * *'`) to automatically reconcile org state with `peribolos.yaml`. The cron time SHOULD avoid top-of-hour slots to reduce GitHub Actions scheduling delays. + +#### Scenario: Daily cron triggers apply +- **GIVEN** the apply workflow is deployed with a daily `schedule` trigger +- **WHEN** the daily cron schedule fires +- **THEN** the workflow generates a fresh installation token (per `token-auth` spec), runs Peribolos with `--confirm`, and applies all settings from `peribolos.yaml` + +#### Scenario: Scheduled run uses same authentication as push-triggered runs +- **GIVEN** the `token-auth` spec's installation token mechanism is deployed +- **WHEN** the daily cron triggers the workflow +- **THEN** the workflow generates a fresh installation token and authenticates identically to a push-triggered run + +#### Scenario: Concurrent workflow runs prevented +- **GIVEN** the apply workflow uses a `concurrency` group +- **WHEN** a push-triggered and cron-triggered run overlap +- **THEN** the later run waits or cancels to prevent simultaneous Peribolos execution + +#### Scenario: Schedule trigger on fork is skipped +- **GIVEN** the workflow is triggered by a cron schedule on a forked repository +- **WHEN** the repository owner is not `complytime` +- **THEN** the apply step is skipped (guarded by repository owner check) diff --git a/openspec/changes/fix-peribolos-implementation/specs/token-auth/spec.md b/openspec/changes/fix-peribolos-implementation/specs/token-auth/spec.md new file mode 100644 index 0000000..91de861 --- /dev/null +++ b/openspec/changes/fix-peribolos-implementation/specs/token-auth/spec.md @@ -0,0 +1,78 @@ +## ADDED Requirements + +### Requirement: Installation token generation per workflow run +The apply workflow SHALL generate a fresh GitHub App installation token at the start of each run using `actions/create-github-app-token` (SHA-pinned per existing workflow conventions) with the `complytime-bot` app credentials stored in `secrets.COMPLYTIME_BOT_CLIENT_ID` and `secrets.COMPLYTIME_BOT_PRIVATE_KEY`, scoped to the `complytime` org owner. + +**Depends on**: GitHub App `complytime-bot` installed with `organization_administration: write`, `members: write`, `administration: write` permissions. + +#### Scenario: Token generated successfully on push to main +- **GIVEN** secrets `COMPLYTIME_BOT_CLIENT_ID` and `COMPLYTIME_BOT_PRIVATE_KEY` are configured and the `complytime-bot` app is installed on the org +- **WHEN** a push to `main` triggers the apply workflow +- **THEN** the workflow generates a valid installation token scoped to the `complytime` org owner + +#### Scenario: Token generated successfully on manual dispatch +- **GIVEN** secrets `COMPLYTIME_BOT_CLIENT_ID` and `COMPLYTIME_BOT_PRIVATE_KEY` are configured and the `complytime-bot` app is installed on the org +- **WHEN** an org admin triggers the workflow manually via `workflow_dispatch` +- **THEN** the workflow generates a valid installation token scoped to the `complytime` org owner + +#### Scenario: Token generation fails due to invalid credentials +- **GIVEN** `secrets.COMPLYTIME_BOT_PRIVATE_KEY` is invalid, missing, or rotated +- **WHEN** the workflow attempts to generate an installation token +- **THEN** the `create-github-app-token` step fails and the workflow run fails with a clear error message + +### Requirement: Safe token file handling +The workflow SHALL write the installation token to a temporary file using shell-safe quoting (environment variable expansion via `env:` block, not inline `${{ }}` expansion in the `run:` block) to prevent injection via token content. The token file SHALL be removed in a cleanup step that runs unconditionally (`if: always()`) to prevent credential persistence after job failure. + +#### Scenario: Token written safely to file +- **GIVEN** the token generation step completed successfully +- **WHEN** the token is written to the auth file +- **THEN** the write uses `echo "$TOKEN" > auth.txt` with `TOKEN` set via the step's `env:` block, not inline secret expansion + +#### Scenario: Token file cleaned up after failure +- **GIVEN** Peribolos exits with a non-zero exit code +- **WHEN** the workflow proceeds to cleanup +- **THEN** the auth file is removed regardless of the Peribolos exit code + +### Requirement: Peribolos uses installation token with require-self disabled +The apply workflow SHALL pass the installation token to Peribolos via `--github-token-path` and SHALL include `--require-self=false` to avoid the incompatible `GET /user` endpoint. The command SHALL include `--min-admins 2` as a compensating safety control. + +#### Scenario: Peribolos authenticates with installation token +- **GIVEN** a valid installation token is written to the auth file +- **WHEN** Peribolos runs with `--require-self=false` and `--min-admins 2` +- **THEN** Peribolos exits with code 0 and the workflow logs show no authentication errors + +### Requirement: Pipeline exit code propagation +The apply workflow step that executes Peribolos SHALL use `set -o pipefail` so that a non-zero exit code from Peribolos propagates through the pipeline and fails the workflow step. + +#### Scenario: Peribolos failure fails the workflow +- **GIVEN** `set -o pipefail` is set in the shell step +- **WHEN** Peribolos exits with a non-zero exit code (e.g., authentication failure, API error) +- **THEN** the workflow step reports failure and the overall workflow run reports failure + +#### Scenario: Peribolos success passes the workflow +- **GIVEN** `set -o pipefail` is set in the shell step +- **WHEN** Peribolos exits with exit code 0 +- **THEN** the workflow step reports success + +### Requirement: Explicit workflow permissions +The apply workflow SHALL declare explicit `permissions:` at the job level with the minimum required scopes (e.g., `contents: read`). The GitHub App installation token handles org-level operations; the workflow's `GITHUB_TOKEN` needs only minimal permissions. + +#### Scenario: Workflow permissions are minimal +- **WHEN** the apply workflow YAML is inspected +- **THEN** explicit `permissions:` are declared at the job level with no unnecessary write scopes + +## REMOVED Requirements + +### Requirement: Remove unused ghproxy sidecar +The apply workflow SHALL NOT start a ghproxy background process, since Peribolos is not configured to route through it. + +#### Scenario: No ghproxy process in workflow +- **WHEN** the apply workflow runs +- **THEN** no ghproxy process is started and Peribolos connects directly to the GitHub API + +### Requirement: Old APP_ACCESS_TOKEN secret is no longer used +The apply workflow SHALL NOT reference `secrets.APP_ACCESS_TOKEN`. The old secret can be removed from the repository settings after migration (see design.md D1 migration plan). + +#### Scenario: Workflow does not reference old secret +- **WHEN** the apply workflow YAML is inspected +- **THEN** there are no references to `APP_ACCESS_TOKEN` diff --git a/openspec/changes/fix-peribolos-implementation/tasks.md b/openspec/changes/fix-peribolos-implementation/tasks.md new file mode 100644 index 0000000..a0c47c1 --- /dev/null +++ b/openspec/changes/fix-peribolos-implementation/tasks.md @@ -0,0 +1,37 @@ +## 1. Fix Authentication and Pipeline + +- [x] 1.1 Replace `APP_ACCESS_TOKEN` with `actions/create-github-app-token` (SHA-pinned) in `apply_peribolos.yml`: add token generation step using `secrets.COMPLYTIME_BOT_CLIENT_ID` and `secrets.COMPLYTIME_BOT_PRIVATE_KEY` scoped to `owner: complytime` +- [x] 1.2 Update the Peribolos execution step to use the generated token via safe file writing (`env:` block, not inline expansion), add `--require-self=false` flag, and ensure `--min-admins 2` is present +- [x] 1.3 Add `set -o pipefail` to the Peribolos execution step to fix silent failure masking +- [x] 1.4 Remove ghproxy build, download, and sidecar process from the workflow (not used by Peribolos) +- [x] 1.5 Add unconditional token file cleanup step (`if: always()`) to remove `auth.txt` after Peribolos runs +- [x] 1.6 Add explicit `permissions:` block at the job level with minimal scopes + +## 2. Add Workflow Triggers + +- [x] 2.1 Add `workflow_dispatch` trigger with a `dry-run` boolean input (default: `false`) to `apply_peribolos.yml` +- [x] 2.2 Add `schedule` trigger with daily cron expression (e.g., `cron: '30 6 * * *'`) to `apply_peribolos.yml` +- [x] 2.3 Conditionally include or exclude `--confirm` flag based on the `dry-run` input; ensure `pull_request` events skip the apply step (preserve existing guard) and `schedule`/`push`/`dispatch` events proceed with apply +- [x] 2.4 Add `concurrency` group to prevent simultaneous Peribolos execution across trigger types + +## 3. Fix Config and Tests + +> Note: Task 3.3 depends on 3.1 and 3.2 being completed first. Wiring `testTeamMembers()` before fixing the config will cause immediate test failures because it validates that org admins are not listed as team members. + +- [x] 3.1 Remove the orphan `docs:` key from `peribolos.yaml` +- [x] 3.2 Move `jpower432` and `marcusburghardt` from `members:` to `maintainers:` in teams where they are org admins (`complytime-approvers`, `complytime-dev`, `openscap-plugin-approvers`) +- [x] 3.3 Wire `testTeamMembers()` into `TestOrgs()` in `config/config_test.go` so team config validation actually runs [depends: 3.1, 3.2] +- [x] 3.4 Run `go test ./config/...` to verify tests pass with the corrected config + +## 4. Drift Detection Workflow + +- [x] 4.1 Create `.github/workflows/drift_detection.yml` with a weekly cron schedule (e.g., `cron: '30 5 * * 1'`) and explicit `permissions: { contents: read, issues: write }` +- [x] 4.2 Implement drift detection: generate installation token for `peribolos --dump complytime`, normalize output, and diff against `peribolos.yaml` +- [x] 4.3 Implement issue management: use `GITHUB_TOKEN` (not App token) to create/update issues with label `peribolos-drift`, wrap diff output in fenced code block for safe rendering, update existing open issue if one exists + +## 5. Validation + +- [x] 5.1 Run the validation workflow (`go test ./config/...`) to confirm all config and test changes are correct +- [x] 5.2 Verify the apply workflow YAML has no references to `APP_ACCESS_TOKEN` or ghproxy, has SHA-pinned action references, and declares explicit permissions + + diff --git a/openspec/schemas/unbound-force/schema.yaml b/openspec/schemas/unbound-force/schema.yaml new file mode 100644 index 0000000..9e3a612 --- /dev/null +++ b/openspec/schemas/unbound-force/schema.yaml @@ -0,0 +1,77 @@ +name: unbound-force +version: 1 +description: > + Unbound Force specification schema with constitution + alignment. Extends the spec-driven workflow with + mandatory governance checks. + +artifacts: + - id: proposal + generates: proposal.md + description: > + Change proposal with constitution alignment + template: proposal.md + instruction: > + Create a change proposal. You MUST include a + Constitution Alignment section assessing this + change against all three org constitution + principles. Read the constitution from + .specify/memory/constitution.md for the full + principle definitions. + requires: [] + + - id: specs + generates: "specs/**/*.md" + description: > + Delta specs describing requirement changes + template: spec.md + instruction: > + Write delta specs using ADDED/MODIFIED/REMOVED + sections. Use RFC 2119 language (MUST/SHALL/ + SHOULD/MAY) for all requirements. Include + Given/When/Then scenarios. + requires: [proposal] + + - id: design + generates: design.md + description: > + Technical design and architecture decisions + template: design.md + instruction: > + Document the technical approach. Reference the + constitution alignment from the proposal. Note + any design decisions that relate to Autonomous + Collaboration, Composability First, or + Observable Quality. + requires: [proposal] + + - id: tasks + generates: tasks.md + description: > + Implementation task checklist + template: tasks.md + instruction: > + Break the design into implementable tasks with + checkboxes. Group related tasks. Include a task + for verifying constitution alignment if the + proposal identified relevant principles. + Add [P] marker after the task number on tasks + that are eligible for parallel execution. + A task is parallel-eligible when it: (a) touches + different files from other [P] tasks in the same + group, (b) has no dependency on prior tasks in + the group, (c) can safely execute without ordering + constraints. Do NOT add [P] when tasks modify the + same file — parallel workers will cause merge + conflicts. Format: - [ ] N.M [P] description. + requires: [specs, design] + +apply: + requires: [tasks] + tracks: tasks.md + instruction: > + Implement tasks from tasks.md. Check off each + task as you complete it. Verify that the + implementation maintains constitution alignment + as documented in the proposal. +# scaffolded by uf vdev diff --git a/openspec/schemas/unbound-force/templates/design.md b/openspec/schemas/unbound-force/templates/design.md new file mode 100644 index 0000000..2165de3 --- /dev/null +++ b/openspec/schemas/unbound-force/templates/design.md @@ -0,0 +1,20 @@ +## Context + + + +## Goals / Non-Goals + +### Goals +- + +### Non-Goals +- + +## Decisions + + + +## Risks / Trade-offs + + + diff --git a/openspec/schemas/unbound-force/templates/proposal.md b/openspec/schemas/unbound-force/templates/proposal.md new file mode 100644 index 0000000..45d5fa9 --- /dev/null +++ b/openspec/schemas/unbound-force/templates/proposal.md @@ -0,0 +1,57 @@ +## Why + + + +## What Changes + + + +## Capabilities + +### New Capabilities +- ``: + +### Modified Capabilities +- ``: + +### Removed Capabilities +- ``: + +## Impact + + + +## Constitution Alignment + +Assessed against the Unbound Force org constitution. + +### I. Autonomous Collaboration + +**Assessment**: PASS | N/A + + + +### II. Composability First + +**Assessment**: PASS | N/A + + + +### III. Observable Quality + +**Assessment**: PASS | N/A + + + +### IV. Testability + +**Assessment**: PASS | N/A + + + diff --git a/openspec/schemas/unbound-force/templates/spec.md b/openspec/schemas/unbound-force/templates/spec.md new file mode 100644 index 0000000..7b1e55a --- /dev/null +++ b/openspec/schemas/unbound-force/templates/spec.md @@ -0,0 +1,23 @@ +## ADDED Requirements + +### Requirement: + + + +#### Scenario: +- **GIVEN** +- **WHEN** +- **THEN** + +## MODIFIED Requirements + +### Requirement: + + + +## REMOVED Requirements + +### Requirement: + + + diff --git a/openspec/schemas/unbound-force/templates/tasks.md b/openspec/schemas/unbound-force/templates/tasks.md new file mode 100644 index 0000000..e5bbfc7 --- /dev/null +++ b/openspec/schemas/unbound-force/templates/tasks.md @@ -0,0 +1,22 @@ + + +## 1. + +- [ ] 1.1 +- [ ] 1.2 [P] +- [ ] 1.3 [P] + +## 2. + +- [ ] 2.1 + diff --git a/peribolos.yaml b/peribolos.yaml index 0afffed..10c370c 100644 --- a/peribolos.yaml +++ b/peribolos.yaml @@ -77,8 +77,6 @@ orgs: default_branch: main description: The ComplyTime website has_projects: true - docs: - description: Documentation teams: security-managers: description: Enterprise security manager team @@ -108,11 +106,12 @@ orgs: complytime-approvers: description: This would be a CODEOWNERS group for cmd complytime privacy: closed + maintainers: + - jpower432 + - marcusburghardt members: - gvauter - hbraswelrh - - jpower432 - - marcusburghardt - sonupreetam - trevor-vaughan repos: @@ -120,19 +119,21 @@ orgs: openscap-plugin-approvers: description: This would be a CODEOWNERS group for cmd openscap-plugin privacy: closed + maintainers: + - marcusburghardt members: - gvauter - - marcusburghardt repos: complyctl: write complytime-dev: description: People working on complytime repo privacy: closed + maintainers: + - jpower432 + - marcusburghardt members: - gvauter - hbraswelrh - - jpower432 - - marcusburghardt - sonupreetam - trevor-vaughan repos: From a8528e61037d9e88c2437d5046008ddf7af511f2 Mon Sep 17 00:00:00 2001 From: Marcus Burghardt Date: Fri, 8 May 2026 16:25:18 +0200 Subject: [PATCH 2/2] fix: address PR review feedback - Add set -o pipefail to drift_detection.yml dump step to prevent silent failures (same class of bug this PR fixes in apply workflow) - Redirect peribolos --dump stderr to log file instead of /dev/null so diagnostics are available on failure - Update task 1.5 to reflect process substitution approach - Fix cron example in scheduled-reconciliation spec to match implementation (30 5 not 30 6) Assisted-by: OpenCode (claude-opus-4-6) Signed-off-by: Marcus Burghardt --- .github/workflows/drift_detection.yml | 3 ++- .../specs/scheduled-reconciliation/spec.md | 2 +- openspec/changes/fix-peribolos-implementation/tasks.md | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/drift_detection.yml b/.github/workflows/drift_detection.yml index 6888edd..561b512 100644 --- a/.github/workflows/drift_detection.yml +++ b/.github/workflows/drift_detection.yml @@ -50,12 +50,13 @@ jobs: env: APP_TOKEN: ${{ steps.app-token.outputs.token }} run: | + set -o pipefail /tmp/peribolos \ --config-path peribolos.yaml \ --require-self=false \ --github-token-path <(printf '%s' "$APP_TOKEN") \ --dump complytime \ - --dump-full 2>/dev/null | yq -P 'sort_keys(..)' > /tmp/org-actual.yaml + --dump-full 2>/tmp/peribolos-dump-stderr.log | yq -P 'sort_keys(..)' > /tmp/org-actual.yaml yq -P 'sort_keys(..)' peribolos.yaml > /tmp/org-expected.yaml - name: Compare org state diff --git a/openspec/changes/fix-peribolos-implementation/specs/scheduled-reconciliation/spec.md b/openspec/changes/fix-peribolos-implementation/specs/scheduled-reconciliation/spec.md index 494fdba..edd5ba3 100644 --- a/openspec/changes/fix-peribolos-implementation/specs/scheduled-reconciliation/spec.md +++ b/openspec/changes/fix-peribolos-implementation/specs/scheduled-reconciliation/spec.md @@ -3,7 +3,7 @@ **Depends on**: `token-auth` (authentication mechanism) ### Requirement: Daily scheduled Peribolos execution -The apply workflow SHALL include a `schedule` trigger with a daily cron expression (e.g., `cron: '30 6 * * *'`) to automatically reconcile org state with `peribolos.yaml`. The cron time SHOULD avoid top-of-hour slots to reduce GitHub Actions scheduling delays. +The apply workflow SHALL include a `schedule` trigger with a daily cron expression (e.g., `cron: '30 5 * * *'`) to automatically reconcile org state with `peribolos.yaml`. The cron time SHOULD avoid top-of-hour slots to reduce GitHub Actions scheduling delays. #### Scenario: Daily cron triggers apply - **GIVEN** the apply workflow is deployed with a daily `schedule` trigger diff --git a/openspec/changes/fix-peribolos-implementation/tasks.md b/openspec/changes/fix-peribolos-implementation/tasks.md index a0c47c1..56b8d9f 100644 --- a/openspec/changes/fix-peribolos-implementation/tasks.md +++ b/openspec/changes/fix-peribolos-implementation/tasks.md @@ -4,7 +4,7 @@ - [x] 1.2 Update the Peribolos execution step to use the generated token via safe file writing (`env:` block, not inline expansion), add `--require-self=false` flag, and ensure `--min-admins 2` is present - [x] 1.3 Add `set -o pipefail` to the Peribolos execution step to fix silent failure masking - [x] 1.4 Remove ghproxy build, download, and sidecar process from the workflow (not used by Peribolos) -- [x] 1.5 Add unconditional token file cleanup step (`if: always()`) to remove `auth.txt` after Peribolos runs +- [x] 1.5 ~~Add unconditional token file cleanup step~~ Eliminated by using process substitution — no credentials written to disk - [x] 1.6 Add explicit `permissions:` block at the job level with minimal scopes ## 2. Add Workflow Triggers