Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
### Fixed

- Declare the anomaly-triage caller's least-privilege write permissions so reusable jobs start and route entries in repositories whose default `GITHUB_TOKEN` is read-only.
9 changes: 9 additions & 0 deletions docs/repo-update-log.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ This log records agent-visible repository changes that should be easy to audit l
- **Propagation:** none | pending <repo/path> | completed <repo/path>
```

## 2026-07-09 - Anomaly-triage caller write permissions

- **Issue/PR:** #106 / #107
- **Branch:** agent/codex/106-anomaly-triage-write-permissions
- **Changed paths:** examples/anomaly-triage.yml, scripts/workflow-structure.test.mjs, .changelog/unreleased/106-anomaly-triage-write-permissions.md, docs/repo-update-log.md
- **What changed:** The managed anomaly-triage caller now grants `contents: read`, `pull-requests: write`, and `issues: write`, which are the exact scopes its reusable workflow needs. Added a structural regression test because read-default consumers previously failed during reusable-workflow expansion before GitHub created any jobs.
- **Verification:** TDD RED: `npm test -- scripts/workflow-structure.test.mjs` → 1 failed / 21 passed on the missing caller permission block. GREEN: the same command → 22 passed / 0 failed after adding the three scopes. `actionlint examples/anomaly-triage.yml` exited 0. Full `npm test` → 9 files / 180 tests / 180 passed. `git diff --check` exited 0 with working-tree LF-to-CRLF warnings only. Live consumer proof remains part of propagation.
- **Propagation:** pending — refresh the `archon-setup` snapshot from the delivered provider source, update every opted-in ArchonVII consumer through normal updater lanes, and prove routing/idempotency in a read-default consumer PR.

## 2026-07-04 - Docs-gate lane (docs-system input) + fragment example cleanup

- **Issue/PR:** #104 / (pending)
Expand Down
5 changes: 5 additions & 0 deletions examples/anomaly-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

permissions:
contents: read
pull-requests: write
issues: write

jobs:
triage:
uses: ArchonVII/github-workflows/.github/workflows/anomaly-triage.yml@v1
Expand Down
16 changes: 16 additions & 0 deletions scripts/workflow-structure.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,22 @@ describe('repo-required-gate caller example', () => {
});
});

describe('anomaly-triage caller permission contract', () => {
it('grants the reusable workflow the least write permissions it requires', () => {
const body = readExample('anomaly-triage');

// Reusable workflows cannot elevate the caller token. Without this block,
// read-default consumers fail at workflow startup before jobs exist (#106).
const permissionsStart = body.indexOf('permissions:');
const jobsStart = body.indexOf('jobs:');
expect(permissionsStart).toBeGreaterThan(-1);
expect(permissionsStart).toBeLessThan(jobsStart);
expect(body.slice(permissionsStart, jobsStart).replaceAll('\r\n', '\n').trim()).toBe(
['permissions:', ' contents: read', ' pull-requests: write', ' issues: write'].join('\n'),
);
});
});

describe('pr-policy workflow contract source', () => {
it('uses the shared PR contract validator instead of inline body regexes', () => {
const body = readWorkflow('pr-policy');
Expand Down