Skip to content

[formal-spec] Add PM-10/AppG formal model coverage for security architecture summary#46280

Merged
pelikhan merged 4 commits into
mainfrom
copilot/formal-spec-security-architecture-summary
Jul 18, 2026
Merged

[formal-spec] Add PM-10/AppG formal model coverage for security architecture summary#46280
pelikhan merged 4 commits into
mainfrom
copilot/formal-spec-security-architecture-summary

Conversation

Copilot AI commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

This updates formal-spec coverage for specs/security-architecture-spec-summary.md by 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

    • Added pkg/workflow/security_architecture_pm10_formal_test.go with focused formal tests for:
      • pre-activation topology and activation gating (PM10a, PM10b)
      • pre-activation read-only permissions (PM10c, including table-driven variants)
      • required-roles default/custom extraction behavior (PM10d)
      • strict-mode write-permission rejection semantics (StrictMode_BlockWritePermissions)
      • lock-file checklist assertions for SHA pinning and fork-filter signal (AppG1, AppG2)
  • Topology/default behavior codified

    • Added explicit coverage for the “no roles configured” path to assert current compiled topology behavior and emitted role env defaults.
  • Pinning/fork-checklist assertions encoded as invariants

    • Added direct assertions over compiled YAML to ensure remote uses: entries are full SHA pinned and that fork-filter configuration is preserved as compiled fork-validation signal.
func TestFormalAppG1_CompiledStepsUseSHAPins(t *testing.T) {
    usesLine := regexp.MustCompile(`(?m)^\s*uses:\s*(\S+)`)
    // ... parse compiled workflow
    assert.True(t, shaRef.MatchString(parts[1]), "remote uses step must be pinned to full SHA")
}

Copilot AI and others added 2 commits July 17, 2026 16:24
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Add formal model and test suite for security architecture [formal-spec] Add PM-10/AppG formal model coverage for security architecture summary Jul 17, 2026
Copilot AI requested a review from pelikhan July 17, 2026 16:40
@pelikhan
pelikhan marked this pull request as ready for review July 18, 2026 05:47
Copilot AI review requested due to automatic review settings July 18, 2026 05:47
@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

Comment on lines +208 to +215
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")
@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 93/100 — Excellent

Analyzed 10 test(s): 9 design, 1 implementation, 0 violation(s).

📊 Metrics (10 tests)
Metric Value
Analyzed 10 (Go: 10, JS: 0)
✅ Design 9 (90%)
⚠️ Implementation 1 (10%)
Edge/error coverage 9 (90%)
Duplicate clusters 0
Inflation N/A (pure test addition)
🚨 Violations 0
📋 Test Classification
Test File Classification Assertions Notes
TestFormalPM10a_PreActivationJobPrecedesActivation security_architecture_pm10_formal_test.go:35 design_test 3 Validates job ordering invariant
TestFormalPM10b_ActivatedOutputExistsAndGatesActivation security_architecture_pm10_formal_test.go:56 design_test 4 Verifies activation gate mechanism
TestFormalPM10c_PreActivationJobHasNoWritePermissions security_architecture_pm10_formal_test.go:79 design_test 2 Enforces read-only permission boundary
TestFormalPM10d_RequiredRolesDefaultToAdminMaintainerWrite security_architecture_pm10_formal_test.go:99 design_test 1 Role default behavior verified
TestFormalPM10d_RequiredRolesCustomFieldHonoured security_architecture_pm10_formal_test.go:111 design_test 1 Custom role configuration validated
TestFormalAppG1_CompiledStepsUseSHAPins security_architecture_pm10_formal_test.go:123 design_test 4 SHA pinning enforcement (AppG security)
TestFormalAppG2_PullRequestTargetContainsForkValidation security_architecture_pm10_formal_test.go:158 implementation_test 1 ⚠️ Only positive case; missing negative scenario
TestFormalStrictMode_WritePermissionsRejected security_architecture_pm10_formal_test.go:175 design_test 6 total (2×3 scopes) Table-driven permission rejection
TestFormalPM10a_PreActivationAbsentWhenNoRoles security_architecture_pm10_formal_test.go:197 design_test 4 No-roles edge case handled
TestFormalPM10c_PreActivationPermissionsTableDriven security_architecture_pm10_formal_test.go:218 design_test 6 total (2×3 variants) Multi-variant permission verification

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 security_architecture_pm10_formal_test.go have correct //go:build !integration tag.

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 Observations

⚠️ TestFormalAppG2_PullRequestTargetContainsForkValidation: Tests only the presence of fork-filtering comment but lacks a negative assertion (e.g., verifying absence when forks key is absent). Consider adding that scenario to complete the formal specification coverage.

Verdict

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 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

🧪 Test quality analysis by Test Quality Sentinel · 12.9 AIC · ⌖ 12.3 AIC · ⊞ 7K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 93/100. 90% design tests (9/10), 10% implementation tests (threshold: 30%). All formal security architecture invariants covered with no code-quality violations.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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
  1. 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.

  2. 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.

  3. StrictMode test uses empty WorkflowData.Permissions (line 299): WorkflowData{Permissions: "permissions: {}"} passes empty permissions alongside a write-carrying perms arg — if the function reads from the struct field, the error expectation proves nothing.

  4. PreActivationAbsentWhenNoRoles name vs assertion contradiction (line 317): function name says absent, but require.NotEmpty asserts 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, &quot;# forks: [] # Fork filtering applied via job conditions&quot;)

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(&amp;WorkflowData{Permissions: &quot;permissions: {}&quot;}, perms)

The WorkflowData.Permissions field is set to &quot;permissions: {}&quot; (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, &quot;pre_activation&quot;)
    require.NotEmpty(t, preActivationSection)  // asserts section IS present

The function name says PreActivationAbsent but the test require.NotEmptys the pre_activatio…

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧠 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")
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/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)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[/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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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:

  • extractRolesrole_checks.go:116, correct signature and default behavior
  • extractJobSectioncompiler_test_helpers_test.go:42, existing test helper
  • validateDangerousPermissionsdangerous_permissions_validation.go:24, correct signature
  • NewPermissions, Permissions.Set, PermissionScope, PermissionWrite — all in permissions*.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 extractRoles implementation 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 conditions is the exact string emitted by frontmatter_on_section_cleanup.go:589
  • StrictModevalidateDangerousPermissions with WorkflowData{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.allowed list 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>
@github-actions

Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (268 new lines in pkg/workflow/) but does not have a linked Architecture Decision Record (ADR).

📄 Draft ADR committed: docs/adr/46280-formal-test-corpus-for-pm10-appg-invariants.md — review and complete it before merging.

🔒 This PR cannot merge until an ADR is linked in the PR body.

📋 What to do next
  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI could not infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-46280: Encode PM-10 and Appendix G Security Invariants as Formal Go Tests

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

❓ Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

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 Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 46280-formal-test-corpus-for-pm10-appg-invariants.md for PR #46280).

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · 70.9 AIC · ⌖ 10.4 AIC · ⊞ 8.5K ·
Comment /review to run again

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address the unresolved review feedback, update the PR body with the required ADR link, and rerun checks once the branch is ready.

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:

  • none listed in compact prefilter snapshot

Generated by PR Sous Chef: https://github.com/github/gh-aw/actions/runs/29633801230

Generated by 👨‍🍳 PR Sous Chef · 17.1 AIC · ⌖ 7.54 AIC · ⊞ 7.5K ·
Comment /souschef to run again

@pelikhan
pelikhan merged commit 75936ce into main Jul 18, 2026
29 of 30 checks passed
@pelikhan
pelikhan deleted the copilot/formal-spec-security-architecture-summary branch July 18, 2026 06:36
Copilot stopped work on behalf of gh-aw-bot due to an error July 18, 2026 06:36
Copilot AI requested a review from gh-aw-bot July 18, 2026 06:36
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.82.13

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[formal-spec] security-architecture-spec-summary.md — Formal model & test suite — 2026-07-17

4 participants