[formal-spec] Add PM-10/AppG formal model coverage for security architecture summary#46280
Conversation
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
✅ Design Decision Gate 🏗️ completed the design decision gate check. |
|
✅ Test Quality Sentinel completed test quality analysis. |
|
✅ PR Code Quality Reviewer completed the code quality review. |
|
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅ |
There was a problem hiding this comment.
Pull request overview
Adds formal tests for PM-10 RBAC and Appendix G security invariants.
Changes:
- Adds pre-activation, role, permission, pinning, fork, and strict-mode tests.
- Refreshes generated action-version annotations in workflow lock files.
Show a summary per file
| File | Description |
|---|---|
pkg/workflow/security_architecture_pm10_formal_test.go |
Adds the formal security test suite. |
.github/workflows/skillet.lock.yml |
Refreshes checkout version metadata. |
.github/workflows/release.lock.yml |
Refreshes setup-go version annotation. |
.github/workflows/hourly-ci-cleaner.lock.yml |
Refreshes checkout version metadata. |
.github/workflows/avenger.lock.yml |
Refreshes checkout version metadata. |
Review details
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 5/5 changed files
- Comments generated: 3
- Review effort level: Medium
| preActivationSection := extractJobSection(yamlOut, "pre_activation") | ||
| require.NotEmpty(t, preActivationSection) | ||
| assert.Contains(t, preActivationSection, `GH_AW_REQUIRED_ROLES: ""`) | ||
|
|
||
| activationSection := extractJobSection(yamlOut, "activation") | ||
| require.NotEmpty(t, activationSection) | ||
| assert.Contains(t, activationSection, "needs: pre_activation") | ||
| assert.Contains(t, activationSection, "needs.pre_activation.outputs.activated") |
| perms := NewPermissions() | ||
| perms.Set(tc.scope, PermissionWrite) | ||
|
|
||
| err := validateDangerousPermissions(&WorkflowData{Permissions: "permissions: {}"}, perms) |
| contents: read | ||
| ---`) | ||
|
|
||
| assert.Contains(t, yamlOut, "# forks: [] # Fork filtering applied via job conditions") |
🧪 Test Quality Sentinel Report✅ Test Quality Score: 93/100 — Excellent
📊 Metrics (10 tests)
📋 Test Classification
Strengths✅ Security-focused design tests: 90% of tests verify formal security architecture guarantees (PM-10/AppG compliance), not just happy-path implementations. ✅ No mock violations: All tests use pure assertion (testify assert/require). No gomock, testify/mock, or .EXPECT() patterns. ✅ Proper build tags: All test functions in ✅ Edge case coverage: Table-driven tests parametrize 3+ variants per test function, validating role defaults, custom roles, multiple permission scopes, and no-roles scenario. ✅ High behavioral value: Each test enforces a distinct security invariant (job ordering, activation gates, permission boundaries, SHA pinning). Minor ObservationsVerdict✅ APPROVED. Test Quality Score: 93/100. Implementation tests: 10% (threshold: 30%). All formal security architecture invariants are covered with no code-quality violations. Excellent behavioral coverage and design-level focus. Warning Firewall blocked 1 domainThe following domain was blocked by the firewall during workflow execution:
network:
allowed:
- defaults
- "awmgmcpg"See Network Configuration for more information.
|
There was a problem hiding this comment.
Several security spec tests have correctness flaws that let them pass vacuously
The new security_architecture_pm10_formal_test.go adds PM-10/AppG formal coverage but four issues weaken or nullify the invariants it claims to enforce.
Blocking issues
-
Hardcoded 6-space indentation in PM10c write-permission regex (line 204): the regex silently misses write-permission lines at any other indent depth — a compiler formatting change would make all PM10c assertions trivially pass.
-
AppG2 asserts on a YAML comment, not executable enforcement (line 281): the assertion only verifies a comment was emitted; no fork-filter
if:condition is checked. -
StrictMode test uses empty WorkflowData.Permissions (line 299):
WorkflowData{Permissions: "permissions: {}"}passes empty permissions alongside a write-carryingpermsarg — if the function reads from the struct field, the error expectation proves nothing. -
PreActivationAbsentWhenNoRoles name vs assertion contradiction (line 317): function name says absent, but
require.NotEmptyasserts the job is present.
🔎 Code quality review by PR Code Quality Reviewer · 47.1 AIC · ⌖ 4.8 AIC · ⊞ 5.6K
Comment /review to run again
Comments that could not be inline-anchored
pkg/workflow/security_architecture_pm10_formal_test.go:204
Hardcoded indentation in write-permission regex silently misses violations at other YAML depths.
<details>
<summary>💡 Details</summary>
The regex ^\s{6}[a-z0-9-]+:\s*write\s*$ hard-codes exactly 6 spaces of indentation. Compiled YAML for job-level permissions is commonly indented at 8 or 10 spaces (inside a job block, inside a permissions: key). If the indentation in the emitted YAML differs from 6, a write permission line will never match and the assertion assert.False(t, ...) w…
pkg/workflow/security_architecture_pm10_formal_test.go:281
AppG2 asserts on a YAML comment string, not on any executable fork-filter enforcement.
<details>
<summary>💡 Details</summary>
assert.Contains(t, yamlOut, "# forks: [] # Fork filtering applied via job conditions")This assertion passes if the compiler emits a particular comment line. Comments are not executed — a workflow that emits this comment but applies no actual if: condition on fork events would satisfy this test. The test proves the compiler adds a comment, not that fo…
pkg/workflow/security_architecture_pm10_formal_test.go:299
StrictMode test passes a WorkflowData with empty permissions string, making the assertion potentially vacuous.
<details>
<summary>💡 Details</summary>
err := validateDangerousPermissions(&WorkflowData{Permissions: "permissions: {}"}, perms)The WorkflowData.Permissions field is set to "permissions: {}" (empty/unset permissions), while the separate perms argument carries the write permissions to validate. If validateDangerousPermissions reads its policy decision from `Wo…
pkg/workflow/security_architecture_pm10_formal_test.go:317
Test named 'PreActivationAbsentWhenNoRoles' contradicts its own assertions — it expects pre_activation to be present.
<details>
<summary>💡 Details</summary>
func TestFormalPM10a_PreActivationAbsentWhenNoRoles(t *testing.T) {
// ...
preActivationSection := extractJobSection(yamlOut, "pre_activation")
require.NotEmpty(t, preActivationSection) // asserts section IS presentThe function name says PreActivationAbsent but the test require.NotEmptys the pre_activatio…
There was a problem hiding this comment.
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 39.7 AIC · ⌖ 4.7 AIC · ⊞ 6.7K
Comment /matt to run again
| err := validateDangerousPermissions(&WorkflowData{Permissions: "permissions: {}"}, perms) | ||
| require.Error(t, err) | ||
| assert.Contains(t, err.Error(), "write permissions") | ||
| }) |
There was a problem hiding this comment.
[/tdd] Test name contradicts its assertion — the name says AbsentWhenNoRoles but require.NotEmpty(t, preActivationSection) asserts the section IS present.
💡 Suggested fix
The test actually validates that pre_activation is still emitted with an empty GH_AW_REQUIRED_ROLES env var when no roles are configured, not that the job is absent. Rename to clarify intent:
func TestFormalPM10a_PreActivationPresentWithEmptyRolesWhenNoRolesConfigured(t *testing.T) {Or, if the intent truly is to assert absence, flip the assertion to require.Empty.
Misleading test names erode trust in the test suite and make failures hard to diagnose.
@copilot please address this.
|
|
||
| func TestFormalAppG2_PullRequestTargetContainsForkValidation(t *testing.T) { | ||
| yamlOut := compileFormalPM10Workflow(t, `--- | ||
| name: formal-appg2 |
There was a problem hiding this comment.
[/tdd] TestFormalAppG1_CompiledStepsUseSHAPins passes vacuously if the minimal compiled workflow happens to emit no remote uses: steps — require.Positive(t, remoteUsesCount) is correct, but if the helper workflow genuinely has no remote actions, the test silently asserts nothing about SHA pinning.
💡 Suggestion
To make the test self-documenting about what it expects, add a comment explaining what remote action(s) are expected in the compiled output, or use a fixture workflow that is guaranteed to include a known remote action:
// compileFormalPM10Workflow always emits github/codeql-action@<sha> as part
// of the engine bootstrap; if that changes this test must be updated.
assert.Positive(t, remoteUsesCount, "expected at least one remote uses step (engine bootstrap action)")Without this, a future refactor that removes the bootstrap action would silently make this test a no-op.
@copilot please address this.
|
|
||
| assert.Equal(t, []string{"admin", "maintainer", "write"}, roles) | ||
| } | ||
|
|
There was a problem hiding this comment.
[/tdd] The regex (?m)^\s{6}[a-z0-9-]+:\s*write\s*$ is fragile — it hard-codes 6 spaces of indentation and will miss permissions serialized with different indentation (e.g., 4 or 8 spaces, tabs).
💡 Suggestion
Use a looser pattern that matches any indentation level:
writePermissionLine := regexp.MustCompile(`(?m)^\s+[a-z0-9-]+:\s*write\s*$`)This also makes the table-driven variant in TestFormalPM10c_PreActivationPermissionsTableDriven (line ~230) more robust.
@copilot please address this.
| }, | ||
| }) | ||
|
|
||
| assert.Equal(t, []string{"triage"}, roles) |
There was a problem hiding this comment.
[/tdd] TestFormalPM10d_RequiredRolesDefaultToAdminMaintainerWrite calls compiler.extractRoles with a map that lacks a top-level roles key entirely, but the frontmatter shape passed in normal usage includes roles under on:. Verify this matches the actual calling convention — a mismatch means the test is exercising a different code path than real workflow compilation.
💡 Suggestion
Cross-check the exact map shape extractRoles receives in production. If it reads on.roles then the test input should be:
roles := compiler.extractRoles(map[string]any{
"on": map[string]any{
"pull_request": map[string]any{"types": []any{"opened"}},
// no "roles" key — this is the default path
},
})The current test omits pull_request inside on, so the default-roles path may never be exercised under PR trigger conditions.
@copilot please address this.
There was a problem hiding this comment.
Review: PM-10/AppG formal model coverage
The new test suite in pkg/workflow/security_architecture_pm10_formal_test.go is well-structured. All referenced types and functions exist in the production code:
extractRoles—role_checks.go:116, correct signature and default behaviorextractJobSection—compiler_test_helpers_test.go:42, existing test helpervalidateDangerousPermissions—dangerous_permissions_validation.go:24, correct signatureNewPermissions,Permissions.Set,PermissionScope,PermissionWrite— all inpermissions*.go
Test logic is sound:
- PM10a/b — topology and activation gating assertions match the compiler's job dependency model
- PM10c — read-only permission regex is appropriate for the YAML indentation produced by the compiler
- PM10d — default and custom role extraction match the
extractRolesimplementation exactly - AppG1 — SHA-pin assertion correctly skips local (
./) and docker references; embedded pin database ensures remote steps are pinned - AppG2 —
# forks: [] # Fork filtering applied via job conditionsis the exact string emitted byfrontmatter_on_section_cleanup.go:589 - StrictMode —
validateDangerousPermissionswithWorkflowData{Permissions: "permissions: {}"}correctly triggers the write-permission check path
Lock file changes are routine manifest hash updates.
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
proxy.golang.org
To allow these domains, add them to the
network.allowedlist in your workflow frontmatter:
network:
allowed:
- defaults
- "proxy.golang.org"See Network Configuration for more information.
🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 71 AIC · ⌖ 4.38 AIC · ⊞ 5K
Adds a draft Architecture Decision Record documenting the decision to encode PM-10 pre-activation RBAC invariants and Appendix G lock-file checklist assertions as formal Go unit tests in the workflow compiler test corpus. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🏗️ Design Decision Gate — ADR RequiredThis PR makes significant changes to core business logic (268 new lines in 📄 Draft ADR committed:
📋 What to do next
Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision. ❓ Why ADRs Matter
ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you. 📋 Michael Nygard ADR Format ReferenceAn ADR must contain these four sections to be considered complete:
All ADRs are stored in
|
|
@copilot please run the Unresolved review feedback:
The latest review comments call out vacuous formal-test assertions and the ADR gate is still asking for the ADR link in the PR body. Please close both gaps on current HEAD before handing back to a maintainer. Failed checks:
|
|
🎉 This pull request is included in a new release. Release: |
This updates formal-spec coverage for
specs/security-architecture-spec-summary.mdby adding explicit tests for PM-10 pre-activation RBAC invariants and Appendix G lock-file checklist assertions that were previously unmodeled in the formal test corpus.New formal suite: PM-10 + Appendix G + strict-mode guards
pkg/workflow/security_architecture_pm10_formal_test.gowith focused formal tests for:PM10a,PM10b)PM10c, including table-driven variants)PM10d)StrictMode_BlockWritePermissions)AppG1,AppG2)Topology/default behavior codified
Pinning/fork-checklist assertions encoded as invariants
uses:entries are full SHA pinned and that fork-filter configuration is preserved as compiled fork-validation signal.