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
2 changes: 1 addition & 1 deletion docs/src/content/docs/reference/frontmatter-full.md
Original file line number Diff line number Diff line change
Expand Up @@ -1511,7 +1511,7 @@ tools:
# Guard policy: minimum required integrity level for repository access. Restricts
# the agent to users with at least the specified permission level.
# (optional)
min-integrity: "reader"
min-integrity: "unapproved"

# GitHub App configuration for token minting. When configured, a GitHub App
# installation access token is minted at workflow start and used instead of the
Expand Down
30 changes: 20 additions & 10 deletions docs/src/content/docs/reference/github-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ tools:
mode: remote
toolsets: [default]
repos: "all"
min-integrity: reader
min-integrity: unapproved
```

Both `repos` and `min-integrity` are required when either is specified.
Expand All @@ -96,19 +96,29 @@ tools:
- "myorg/*"
- "partner/shared-repo"
- "myorg/api-*"
min-integrity: writer
min-integrity: approved
```

### `min-integrity`

Sets the minimum integrity level required for repository access:
Sets the minimum integrity level required for repository access.

| Level | Description |
|-------|-------------|
| `none` | No integrity requirements |
| `reader` | Read-level integrity |
| `writer` | Write-level integrity |
| `merged` | Merged-level integrity |
#### Integrity Level Definitions

Integrity levels are determined based on the combination of the `author_association` field associated with GitHub objects (issues, pull requests, comments, etc.) and whether an object is reachable from the main branch:

| Level | Criteria |
|-------|----------|
| `merged` | Objects reachable from the main branch (regardless of authorship) |
| `approved` | Objects with `author_association` of `OWNER`, `MEMBER`, or `COLLABORATOR` |
| `unapproved` | Objects with `author_association` of `CONTRIBUTOR` or `FIRST_TIME_CONTRIBUTOR` |
| `none` | Objects with `author_association` of `FIRST_TIMER` or `NONE` |

**How it works:**
- **Merged content** has the highest integrity because it has been reviewed and merged into the main branch
- **Approved contributors** (owners, members, collaborators) have established trust relationships with the repository
- **Unapproved contributors** have made contributions but lack formal repository access
- **None level** includes first-time interactions and users with no prior contribution history

### Examples

Expand All @@ -131,7 +141,7 @@ tools:
repos:
- "frontend-org/*"
- "backend-org/*"
min-integrity: writer
min-integrity: approved
```

## Lockdown Mode for Public Repositories
Expand Down
2 changes: 1 addition & 1 deletion pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3037,7 +3037,7 @@
"min-integrity": {
"type": "string",
"description": "Guard policy: minimum required integrity level for repository access. Restricts the agent to users with at least the specified permission level.",
"enum": ["reader", "writer", "maintainer", "admin"]
"enum": ["none", "unapproved", "approved", "merged"]
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description states "reader → unapproved, writer → approved, merged and none remain unchanged," but the previous schema enum was ["reader", "writer", "maintainer", "admin"]. The values merged and none were not present in the old schema at all, and maintainer and admin are removed with no documented mapping. The PR description is inaccurate regarding what was changed in the schema — this discrepancy may be confusing to reviewers and contributors trying to understand the migration path.

Copilot uses AI. Check for mistakes.
},
"github-app": {
"type": "object",
Expand Down
8 changes: 4 additions & 4 deletions pkg/workflow/tools_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,10 +268,10 @@ type GitHubIntegrityLevel string
const (
// GitHubIntegrityNone allows access with no integrity requirements
GitHubIntegrityNone GitHubIntegrityLevel = "none"
// GitHubIntegrityReader requires read-level integrity
GitHubIntegrityReader GitHubIntegrityLevel = "reader"
// GitHubIntegrityWriter requires write-level integrity
GitHubIntegrityWriter GitHubIntegrityLevel = "writer"
// GitHubIntegrityUnapproved requires unapproved-level integrity
GitHubIntegrityUnapproved GitHubIntegrityLevel = "unapproved"
// GitHubIntegrityApproved requires approved-level integrity
GitHubIntegrityApproved GitHubIntegrityLevel = "approved"
// GitHubIntegrityMerged requires merged-level integrity
GitHubIntegrityMerged GitHubIntegrityLevel = "merged"
)
Expand Down
12 changes: 6 additions & 6 deletions pkg/workflow/tools_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,20 +87,20 @@ func validateGitHubGuardPolicy(tools *Tools, workflowName string) error {
// Validate min-integrity field (required when repos is set)
if !hasMinIntegrity {
toolsValidationLog.Printf("Missing min-integrity in guard policy for workflow: %s", workflowName)
return errors.New("invalid guard policy: 'github.min-integrity' is required. Valid values: 'none', 'reader', 'writer', 'merged'")
return errors.New("invalid guard policy: 'github.min-integrity' is required. Valid values: 'none', 'unapproved', 'approved', 'merged'")
}

// Validate min-integrity value
validIntegrityLevels := map[GitHubIntegrityLevel]bool{
GitHubIntegrityNone: true,
GitHubIntegrityReader: true,
GitHubIntegrityWriter: true,
GitHubIntegrityMerged: true,
GitHubIntegrityNone: true,
GitHubIntegrityUnapproved: true,
GitHubIntegrityApproved: true,
GitHubIntegrityMerged: true,
}

if !validIntegrityLevels[github.MinIntegrity] {
toolsValidationLog.Printf("Invalid min-integrity level '%s' in workflow: %s", github.MinIntegrity, workflowName)
return errors.New("invalid guard policy: 'github.min-integrity' must be one of: 'none', 'reader', 'writer', 'merged'. Got: '" + string(github.MinIntegrity) + "'")
return errors.New("invalid guard policy: 'github.min-integrity' must be one of: 'none', 'unapproved', 'approved', 'merged'. Got: '" + string(github.MinIntegrity) + "'")
}

return nil
Expand Down
14 changes: 7 additions & 7 deletions pkg/workflow/tools_validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ func TestValidateGitHubGuardPolicy(t *testing.T) {
toolsMap: map[string]any{
"github": map[string]any{
"repos": "all",
"min-integrity": "reader",
"min-integrity": "unapproved",
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The integration test file pkg/workflow/guard_policy_experimental_warning_test.go (not included in this PR) still uses the old reader and writer integrity level values at lines 32 and 80. After this PR's changes, those values are no longer valid, which will cause the integration tests TestGuardPolicyExperimentalWarning to fail — the test cases that use these values expect compilation to succeed, but the updated validation will now return a validation error for both. Those test cases need to be updated to use unapproved and approved respectively.

Copilot uses AI. Check for mistakes.
},
},
shouldError: false,
Expand All @@ -275,7 +275,7 @@ func TestValidateGitHubGuardPolicy(t *testing.T) {
toolsMap: map[string]any{
"github": map[string]any{
"repos": "public",
"min-integrity": "writer",
"min-integrity": "approved",
},
},
shouldError: false,
Expand Down Expand Up @@ -304,7 +304,7 @@ func TestValidateGitHubGuardPolicy(t *testing.T) {
name: "missing repos field",
toolsMap: map[string]any{
"github": map[string]any{
"min-integrity": "reader",
"min-integrity": "unapproved",
},
},
shouldError: true,
Expand Down Expand Up @@ -336,7 +336,7 @@ func TestValidateGitHubGuardPolicy(t *testing.T) {
toolsMap: map[string]any{
"github": map[string]any{
"repos": "private",
"min-integrity": "reader",
"min-integrity": "unapproved",
},
},
shouldError: true,
Expand All @@ -347,7 +347,7 @@ func TestValidateGitHubGuardPolicy(t *testing.T) {
toolsMap: map[string]any{
"github": map[string]any{
"repos": []any{},
"min-integrity": "reader",
"min-integrity": "unapproved",
},
},
shouldError: true,
Expand All @@ -358,7 +358,7 @@ func TestValidateGitHubGuardPolicy(t *testing.T) {
toolsMap: map[string]any{
"github": map[string]any{
"repos": []any{"Owner/repo"},
"min-integrity": "reader",
"min-integrity": "unapproved",
},
},
shouldError: true,
Expand All @@ -369,7 +369,7 @@ func TestValidateGitHubGuardPolicy(t *testing.T) {
toolsMap: map[string]any{
"github": map[string]any{
"repos": []any{"just-a-name"},
"min-integrity": "reader",
"min-integrity": "unapproved",
},
},
shouldError: true,
Expand Down
10 changes: 8 additions & 2 deletions scratchpad/dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -1706,7 +1706,7 @@ tools:
mode: remote
toolsets: [default]
repos: "all" # "all", "public", or array of patterns
min-integrity: reader # none | reader | writer | merged
min-integrity: unapproved # none | unapproved | approved | merged
```

Both `repos` and `min-integrity` are required when either is specified under `github:`.
Expand All @@ -1721,7 +1721,13 @@ Pattern validation rules:
- Wildcards are only permitted at the end of the repo name segment
- Empty arrays are not allowed

**Integrity Levels**: `none` | `reader` | `writer` | `merged` (case-sensitive)
**Integrity Levels**: `none` | `unapproved` | `approved` | `merged` (case-sensitive)

Integrity levels are determined by the `author_association` field and main branch reachability:
- `merged`: Objects reachable from the main branch (highest integrity)
- `approved`: `author_association` of `OWNER`, `MEMBER`, or `COLLABORATOR`
- `unapproved`: `author_association` of `CONTRIBUTOR` or `FIRST_TIME_CONTRIBUTOR`
- `none`: `author_association` of `FIRST_TIMER` or `NONE` (lowest integrity)

**Validation Location**: `pkg/workflow/tools_validation.go` — `validateGitHubGuardPolicy()` runs during workflow compilation via `compiler_orchestrator_workflow.go` and `compiler_string_api.go`.

Expand Down
27 changes: 15 additions & 12 deletions scratchpad/guard-policies-specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,13 @@ Based on the provided JSON schema, the implementation supports:
- `"owner/prefix*"` - Repositories with name prefix under owner

**Integrity Levels:**
- `"none"` - No min-integrity requirements
- `"reader"` - Read-level integrity
- `"writer"` - Write-level integrity
- `"merged"` - Merged-level integrity

Integrity levels are based on the combination of the `author_association` field associated with GitHub objects and whether an object is reachable from the main branch:

- `"merged"` - Objects reachable from the main branch (highest integrity, regardless of authorship)
- `"approved"` - Objects with `author_association` of `OWNER`, `MEMBER`, or `COLLABORATOR`
- `"unapproved"` - Objects with `author_association` of `CONTRIBUTOR` or `FIRST_TIME_CONTRIBUTOR`
- `"none"` - Objects with `author_association` of `FIRST_TIMER` or `NONE` (lowest integrity)

### 3. Frontmatter Syntax

Expand All @@ -56,7 +59,7 @@ tools:
mode: remote
toolsets: [default]
repos: "all"
min-integrity: reader
min-integrity: unapproved
```

**With Repository Patterns:**
Expand All @@ -69,7 +72,7 @@ tools:
- "myorg/*"
- "partner/shared-repo"
- "docs/api-*"
min-integrity: writer
min-integrity: approved
```

**Public Repositories Only:**
Expand All @@ -89,7 +92,7 @@ tools:

2. **Validation** (`tools_validation.go`):
- Validates repos format (all/public or valid patterns)
- Validates min-integrity level (none/reader/writer/merged)
- Validates min-integrity level (none/unapproved/approved/merged)
- Validates repository pattern syntax (lowercase, valid characters, wildcard placement)
- Called during workflow compilation

Expand Down Expand Up @@ -173,7 +176,7 @@ The design supports future MCP servers (Jira, WorkIQ) through:
- Empty arrays not allowed

**Integrity Levels:**
- Must be one of: `none`, `reader`, `writer`, `merged`
- Must be one of: `none`, `unapproved`, `approved`, `merged`
- Case-sensitive

**Required Fields:**
Expand All @@ -192,7 +195,7 @@ invalid guard policy: repository pattern 'Owner/Repo' must be lowercase
invalid guard policy: repository pattern 'owner/re*po' has wildcard in the middle.
Wildcards only allowed at the end (e.g., 'prefix*')

invalid guard policy: 'github.min-integrity' must be one of: 'none', 'reader', 'writer', 'merged'.
invalid guard policy: 'github.min-integrity' must be one of: 'none', 'unapproved', 'approved', 'merged'.
Got: 'admin'
```

Expand All @@ -207,7 +210,7 @@ tools:
toolsets: [default]
repos:
- "myorg/*"
min-integrity: reader
min-integrity: unapproved
```

### Example 2: Multiple Organizations
Expand All @@ -221,7 +224,7 @@ tools:
- "frontend-org/*"
- "backend-org/*"
- "shared/infrastructure"
min-integrity: writer
min-integrity: approved
```

### Example 3: Public Repositories Only
Expand All @@ -245,7 +248,7 @@ tools:
repos:
- "myorg/api-*" # Matches api-gateway, api-service, etc.
- "myorg/web-*" # Matches web-frontend, web-backend, etc.
min-integrity: writer
min-integrity: approved
```

## Testing Strategy
Expand Down
Loading