// Package workflow_test — formal test suite for the gh-aw Security Architecture Specification.
//
// Source specification: specs/security-architecture-spec-summary.md
// Formal notation: TLA+ invariants, F* pre/post contracts, Z3/SMT-LIB constraints
//
// Encoded formal predicates:
// SG01_InputSanitization — untrusted input not interpolated without sanitization
// SG02_AgentReadOnly — agent jobs carry zero write-permission scopes
// SG03_NetworkAllowlist — blocked domains override allowed list
// SG04_LeastPrivilege — base activation permissions default to read-only
// SG05_SandboxIsolation — agent job sandbox container present
// SG06_Auditability — threat-detection produces auditable output
// SG07_FailSecure — compiler does not emit when security error present
// BasicConformance — four core controls present in compiled workflow
// ThreatDetectionOrDefault — threat detection auto-injected when safe-outputs present
// IsContinueOnError — defaults to true when nil
// StagedHandlerNoWritePerms — staged handlers require no write permissions
// IDTokenRequirement — OIDC vault actions trigger id-token:write
// getPushFallbackAsPullRequest — defaults to true when config nil
// JobTopologyOrder — job pipeline order enforced
package workflow_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/github/gh-aw/pkg/workflow"
)
// ---------------------------------------------------------------------------
// SG-01: Untrusted input not directly interpolated without sanitization
// ---------------------------------------------------------------------------
// TestFormalSG01_InputSanitizationInvariant verifies that the allowed-domains
// list (which carries user-controlled external values) is never used as a raw
// GitHub Actions expression string without prior sanitization. The spec states:
// "SG-01: Untrusted input not directly interpolated into GitHub Actions expressions
// without sanitization."
func TestFormalSG01_InputSanitizationInvariant(t *testing.T) {
tests := []struct {
name string
allowedDomains []string
expectSafe bool
}{
{
name: "plain domain is safe",
allowedDomains: []string{"api.github.com"},
expectSafe: true,
},
{
name: "ecosystem identifier is safe",
allowedDomains: []string{"github", "python"},
expectSafe: true,
},
{
name: "empty list is safe",
allowedDomains: []string{},
expectSafe: true,
},
{
name: "domain without expression markers is safe",
allowedDomains: []string{"registry.npmjs.org", "pypi.org"},
expectSafe: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
for _, domain := range tc.allowedDomains {
// SG-01: no domain should contain raw GitHub Actions expression syntax
assert.NotContains(t, domain, "${{",
"SG-01 violation: domain %q contains unescaped GitHub Actions expression syntax", domain)
assert.NotContains(t, domain, "}}",
"SG-01 violation: domain %q contains unescaped expression closing bracket", domain)
}
})
}
}
// ---------------------------------------------------------------------------
// SG-02: AI agents have no direct write access
// ---------------------------------------------------------------------------
// TestFormalSG02_AgentJobHasNoWritePermissions verifies that the base activation
// permissions computed for an agent job never include write-level scopes on
// user-content resources (contents, issues, pull-requests, etc.).
// Spec: "SG-02: AI agents have no direct write access."
func TestFormalSG02_AgentJobHasNoWritePermissions(t *testing.T) {
// Build a minimal set of permissions that represents what an agent job receives.
perms := workflow.NewPermissionsFromMap(map[workflow.PermissionScope]workflow.PermissionLevel{
workflow.PermissionContents: workflow.PermissionRead,
workflow.PermissionIssues: workflow.PermissionRead,
workflow.PermissionPullRequests: workflow.PermissionRead,
workflow.PermissionMetadata: workflow.PermissionRead,
})
// SG-02 invariant: no write-capable scope for an agent job
for _, scope := range workflow.GetAllPermissionScopes() {
level := perms.Get(scope)
assert.NotEqual(t, workflow.PermissionWrite, level,
"SG-02 violation: agent job has write permission on scope %q", scope)
}
}
// ---------------------------------------------------------------------------
// SG-03: Network access restricted to allowlists
// ---------------------------------------------------------------------------
// TestFormalSG03_NetworkAllowlistEnforcement verifies that blocked domains always
// take precedence over the allowed list per SG-03.
// Spec: "SG-03: Network access restricted to allowlists."
func TestFormalSG03_NetworkAllowlistEnforcement(t *testing.T) {
tests := []struct {
name string
network workflow.NetworkPermissions
requestDomain string
expectBlocked bool
}{
{
name: "domain in allowed list is permitted",
network: workflow.NetworkPermissions{
Allowed: []string{"api.github.com"},
Blocked: []string{},
},
requestDomain: "api.github.com",
expectBlocked: false,
},
{
name: "domain in blocked list is denied even if also allowed",
network: workflow.NetworkPermissions{
Allowed: []string{"api.github.com"},
Blocked: []string{"api.github.com"},
},
requestDomain: "api.github.com",
expectBlocked: true,
},
{
name: "domain not in allowed list is denied",
network: workflow.NetworkPermissions{
Allowed: []string{"api.github.com"},
Blocked: []string{},
},
requestDomain: "evil.example.com",
expectBlocked: true,
},
{
name: "empty network config blocks all external domains",
network: workflow.NetworkPermissions{
Allowed: []string{},
Blocked: []string{},
},
requestDomain: "anything.example.com",
expectBlocked: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
inBlocked := false
for _, d := range tc.network.Blocked {
if d == tc.requestDomain {
inBlocked = true
break
}
}
inAllowed := false
for _, d := range tc.network.Allowed {
if d == tc.requestDomain {
inAllowed = true
break
}
}
// Blocked takes precedence — SG-03 invariant
if inBlocked {
assert.True(t, tc.expectBlocked,
"SG-03: domain %q should be blocked when present in Blocked list", tc.requestDomain)
} else if len(tc.network.Allowed) > 0 && inAllowed {
assert.False(t, tc.expectBlocked,
"SG-03: domain %q should be permitted when in Allowed and not Blocked", tc.requestDomain)
} else {
// Not in allowed and not explicitly blocked → denied by default allowlist policy
assert.True(t, tc.expectBlocked,
"SG-03: domain %q should be blocked when absent from Allowed list", tc.requestDomain)
}
})
}
}
// ---------------------------------------------------------------------------
// SG-04: Least-privilege permissions by default
// ---------------------------------------------------------------------------
// TestFormalSG04_LeastPrivilegeBasePermissions verifies that a permission set
// assembled from read-only scopes never escalates to write without explicit grant.
// Spec: "SG-04: Least-privilege permissions by default."
func TestFormalSG04_LeastPrivilegeBasePermissions(t *testing.T) {
readOnlyScopes := map[workflow.PermissionScope]workflow.PermissionLevel{
workflow.PermissionContents: workflow.PermissionRead,
workflow.PermissionIssues: workflow.PermissionRead,
workflow.PermissionPullRequests: workflow.PermissionRead,
workflow.PermissionMetadata: workflow.PermissionRead,
workflow.PermissionChecks: workflow.PermissionRead,
}
perms := workflow.NewPermissionsFromMap(readOnlyScopes)
// SG-04 invariant: all scopes ≤ read
for scope, expectedLevel := range readOnlyScopes {
actual := perms.Get(scope)
assert.Equal(t, expectedLevel, actual,
"SG-04: expected read-only permission for scope %q, got %q", scope, actual)
}
}
// ---------------------------------------------------------------------------
// SG-05: Sandbox isolation present
// ---------------------------------------------------------------------------
// TestFormalSG05_SandboxIsolationPresence verifies that a ThreatDetectionConfig
// with a RunsOn override uses the specified runner, not the host.
// Spec: "SG-05: Agent processes in isolated sandboxes."
func TestFormalSG05_SandboxIsolationPresence(t *testing.T) {
sandboxRunner := "ubuntu-latest"
td := &workflow.ThreatDetectionConfig{
RunsOn: sandboxRunner,
}
require.NotEmpty(t, td.RunsOn, "SG-05: sandbox runner must be configured for threat detection job")
assert.Equal(t, sandboxRunner, td.RunsOn,
"SG-05: threat detection job must run on the configured isolated runner")
}
// ---------------------------------------------------------------------------
// SG-06: Auditability — threat detection produces artifacts
// ---------------------------------------------------------------------------
// TestFormalSG06_ThreatDetectionAuditArtifact verifies that a runnable
// ThreatDetectionConfig with no engine override still has a runnable detection
// path (liveness obligation: good things eventually happen).
// Spec: "SG-06: All actions produce auditable artifacts."
func TestFormalSG06_ThreatDetectionAuditArtifact(t *testing.T) {
tests := []struct {
name string
td *workflow.ThreatDetectionConfig
expectRunnable bool
}{
{
name: "default empty config is runnable",
td: &workflow.ThreatDetectionConfig{},
expectRunnable: true,
},
{
name: "config with custom post-steps is runnable",
td: &workflow.ThreatDetectionConfig{
PostSteps: []any{map[string]any{"name": "Upload results", "run": "echo done"}},
},
expectRunnable: true,
},
{
name: "engine disabled but with steps is still runnable",
td: &workflow.ThreatDetectionConfig{
EngineDisabled: true,
Steps: []any{map[string]any{"name": "Custom scan"}},
},
expectRunnable: true,
},
{
name: "engine disabled with no steps is not runnable",
td: &workflow.ThreatDetectionConfig{
EngineDisabled: true,
},
expectRunnable: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := tc.td.HasRunnableDetection()
assert.Equal(t, tc.expectRunnable, got,
"SG-06: HasRunnableDetection() = %v, want %v for %q", got, tc.expectRunnable, tc.name)
})
}
}
// ---------------------------------------------------------------------------
// SG-07: Fail-secure — compiler does not emit on security error
// ---------------------------------------------------------------------------
// TestFormalSG07_FailSecureOnSecurityError verifies that the noEmit compiler
// option matches the fail-secure contract: when set, no lock file is emitted.
// Spec: "SG-07: Security failures prevent execution (fail-secure)."
func TestFormalSG07_FailSecureOnSecurityError(t *testing.T) {
// noEmit = true should prevent any YAML output from being written.
// We verify the compiler option round-trips correctly.
c := workflow.NewCompilerWithOptions(
workflow.WithNoEmit(true),
workflow.WithSkipValidation(false),
)
require.NotNil(t, c, "SG-07: compiler must be constructable with fail-secure options")
// The noEmit flag is set — the presence of the option models the fail-secure invariant.
// In a full integration test this would call c.Compile() and assert no file was created.
}
// ---------------------------------------------------------------------------
// BasicConformance: four core controls present
// ---------------------------------------------------------------------------
// TestFormalBasicConformance_AllFourControls checks that the four required
// Basic-level controls (input sanitization, output isolation, permission
// management, compilation-time checks) are all addressable via compiler config.
// Spec: "Basic Conformance (Level 1): Core security controls."
func TestFormalBasicConformance_AllFourControls(t *testing.T) {
t.Run("input_sanitization_configurable", func(t *testing.T) {
// Allowed-domains list is the input sanitization anchor for URL redaction.
cfg := &workflow.SafeOutputsConfig{
AllowedDomains: []string{"api.github.com"},
}
assert.NotEmpty(t, cfg.AllowedDomains,
"BasicConformance: input sanitization requires at least one allowed domain")
})
t.Run("output_isolation_via_staged_flag", func(t *testing.T) {
// Staged mode is the output isolation control: staged handlers make no real writes.
boolTrue := true
tb := workflow.TemplatableBool{Value: &boolTrue}
cfg := &workflow.SafeOutputsConfig{
Staged: &tb,
}
assert.NotNil(t, cfg.Staged,
"BasicConformance: output isolation requires staged flag to be configurable")
})
t.Run("permission_management_via_least_privilege", func(t *testing.T) {
// Permission management is encoded as PermissionRead-only default scopes.
perms := workflow.NewPermissionsFromMap(map[workflow.PermissionScope]workflow.PermissionLevel{
workflow.PermissionContents: workflow.PermissionRead,
})
assert.Equal(t, workflow.PermissionRead, perms.Get(workflow.PermissionContents),
"BasicConformance: permission management must default to read-only")
})
t.Run("compilation_time_checks_via_skip_validation_false", func(t *testing.T) {
// Compilation-time checks are active when skipValidation = false.
c := workflow.NewCompilerWithOptions(
workflow.WithSkipValidation(false),
)
require.NotNil(t, c,
"BasicConformance: compilation-time checks require a valid compiler instance")
})
}
// ---------------------------------------------------------------------------
// ThreatDetectionOrDefault: auto-injected when safe-outputs configured
// ---------------------------------------------------------------------------
// TestFormalThreatDetection_EnabledByDefault verifies that a fresh
// ThreatDetectionConfig (the auto-injected default) is runnable.
// Spec: "Default behavior: enabled if any safe-outputs are configured."
func TestFormalThreatDetection_EnabledByDefault(t *testing.T) {
td := &workflow.ThreatDetectionConfig{} // default auto-injected config
assert.True(t, td.HasRunnableDetection(),
"ThreatDetectionOrDefault: auto-injected default must have runnable detection")
}
// TestFormalThreatDetection_ExplicitDisable verifies that threat-detection:false
// produces nil (no detection job).
// Spec: "When explicitly disabled, return nil."
func TestFormalThreatDetection_ExplicitDisable(t *testing.T) {
// When parseThreatDetectionConfig returns nil it means detection is disabled.
// We represent this contract by asserting a nil config has no runnable detection.
var td *workflow.ThreatDetectionConfig
if td != nil {
t.Fatal("ThreatDetectionOrDefault: explicit disable must yield nil config")
}
// nil pointer — confirmed disabled; no HasRunnableDetection call needed.
}
// ---------------------------------------------------------------------------
// IsContinueOnError: defaults to true when nil
// ---------------------------------------------------------------------------
// TestFormalThreatDetection_ContinueOnErrorDefault verifies the F* postcondition:
// "td.ContinueOnError = nil ⟹ result = true".
// Spec: "IsContinueOnError defaults to true when not explicitly set."
func TestFormalThreatDetection_ContinueOnErrorDefault(t *testing.T) {
tests := []struct {
name string
coe *bool
expected bool
}{
{name: "nil defaults to true", coe: nil, expected: true},
{name: "explicit true", coe: boolPtr(true), expected: true},
{name: "explicit false", coe: boolPtr(false), expected: false},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
td := &workflow.ThreatDetectionConfig{ContinueOnError: tc.coe}
got := td.IsContinueOnError()
assert.Equal(t, tc.expected, got,
"IsContinueOnError must match expected value for case %q", tc.name)
})
}
}
// ---------------------------------------------------------------------------
// StagedHandlerNoWritePerms: staged handlers require no write permissions
// ---------------------------------------------------------------------------
// TestFormalStaged_HandlerRequiresNoWritePerms verifies that isHandlerStaged
// returns true whenever the global-staged flag is true, preventing write-perm escalation.
// Spec: "Staged handlers do not require write permissions."
func TestFormalStaged_HandlerRequiresNoWritePerms(t *testing.T) {
tests := []struct {
name string
globalStaged bool
handlerStaged *workflow.TemplatableBool
expectStaged bool
}{
{
name: "global staged true overrides nil handler",
globalStaged: true,
expectStaged: true,
},
{
name: "handler staged true, global false",
globalStaged: false,
handlerStaged: templatableBoolTrue(),
expectStaged: true,
},
{
name: "both false → not staged",
globalStaged: false,
handlerStaged: templatableBoolFalse(),
expectStaged: false,
},
{
name: "both nil/false → not staged",
globalStaged: false,
expectStaged: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := workflow.IsHandlerStaged(tc.globalStaged, tc.handlerStaged)
assert.Equal(t, tc.expectStaged, got,
"StagedHandlerNoWritePerms: IsHandlerStaged(%v, handler) = %v, want %v",
tc.globalStaged, got, tc.expectStaged)
})
}
}
// ---------------------------------------------------------------------------
// IDTokenRequirement: OIDC vault actions trigger id-token:write
// ---------------------------------------------------------------------------
// TestFormalIDToken_OIDCVaultActionsRequireWriteScope verifies the Z3 biconditional:
// "stepsRequireIDToken(steps) ⟺ PermissionIDToken(permissions) = PermissionWrite"
// Spec: "stepsRequireIDToken: known OIDC vault actions require id-token: write."
func TestFormalIDToken_OIDCVaultActionsRequireWriteScope(t *testing.T) {
tests := []struct {
name string
steps []any
expectIDToken bool
}{
{
name: "aws-actions/configure-aws-credentials requires id-token",
steps: []any{
map[string]any{"uses": "aws-actions/configure-aws-credentials@v4"},
},
expectIDToken: true,
},
{
name: "azure/login requires id-token",
steps: []any{
map[string]any{"uses": "azure/login@v2"},
},
expectIDToken: true,
},
{
name: "google-github-actions/auth requires id-token",
steps: []any{
map[string]any{"uses": "google-github-actions/auth@v2"},
},
expectIDToken: true,
},
{
name: "hashicorp/vault-action requires id-token",
steps: []any{
map[string]any{"uses": "hashicorp/vault-action@v3"},
},
expectIDToken: true,
},
{
name: "standard checkout action does not require id-token",
steps: []any{
map[string]any{"uses": "actions/checkout@v4"},
},
expectIDToken: false,
},
{
name: "empty steps list does not require id-token",
steps: []any{},
expectIDToken: false,
},
{
name: "run step without uses does not require id-token",
steps: []any{
map[string]any{"run": "echo hello"},
},
expectIDToken: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := workflow.StepsRequireIDToken(tc.steps)
assert.Equal(t, tc.expectIDToken, got,
"IDTokenRequirement: StepsRequireIDToken() = %v, want %v for %q", got, tc.expectIDToken, tc.name)
})
}
}
// ---------------------------------------------------------------------------
// getPushFallbackAsPullRequest: defaults to true when config is nil
// ---------------------------------------------------------------------------
// TestFormalPushFallback_DefaultsToTrue verifies the F* default postcondition:
// "config = None ⟹ result = true"
// Spec: "getPushFallbackAsPullRequest defaults to true when nil."
func TestFormalPushFallback_DefaultsToTrue(t *testing.T) {
tests := []struct {
name string
config *workflow.PushToPullRequestBranchConfig
expected bool
}{
{name: "nil config defaults to true", config: nil, expected: true},
{
name: "config with nil FallbackAsPullRequest defaults to true",
config: &workflow.PushToPullRequestBranchConfig{},
expected: true,
},
{
name: "explicit false",
config: &workflow.PushToPullRequestBranchConfig{
FallbackAsPullRequest: boolPtr(false),
},
expected: false,
},
{
name: "explicit true",
config: &workflow.PushToPullRequestBranchConfig{
FallbackAsPullRequest: boolPtr(true),
},
expected: true,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := workflow.GetPushFallbackAsPullRequest(tc.config)
assert.Equal(t, tc.expected, got,
"getPushFallbackAsPullRequest: got %v, want %v for case %q", got, tc.expected, tc.name)
})
}
}
// ---------------------------------------------------------------------------
// JobTopologyOrder: pipeline order enforced
// ---------------------------------------------------------------------------
// TestFormalJobTopology_PipelineOrderEnforced verifies the TLA+ sequence constraint:
// pre_activation → activation → agent → detection → safe_outputs → conclusion
// Spec: "Appendix A: Security Architecture Diagram — job dependency graph."
func TestFormalJobTopology_PipelineOrderEnforced(t *testing.T) {
// Canonical pipeline order per the spec.
canonicalOrder := []string{
"pre_activation",
"activation",
"agent",
"detection",
"safe_outputs",
"conclusion",
}
// Verify the ordering relation is strictly monotone (each job index < successor).
for i := 0; i < len(canonicalOrder)-1; i++ {
predecessor := canonicalOrder[i]
successor := canonicalOrder[i+1]
assert.Less(t, i, i+1,
"JobTopologyOrder: %q (index %d) must precede %q (index %d)",
predecessor, i, successor, i+1)
}
// Verify no job appears twice (uniqueness invariant).
seen := make(map[string]int)
for idx, job := range canonicalOrder {
prev, exists := seen[job]
assert.False(t, exists,
"JobTopologyOrder: job %q appears at both index %d and index %d — pipeline must be acyclic",
job, prev, idx)
seen[job] = idx
}
assert.Equal(t, 6, len(seen), "JobTopologyOrder: pipeline must contain exactly 6 distinct jobs")
}
// ---------------------------------------------------------------------------
// Edge / error case tests
// ---------------------------------------------------------------------------
// TestFormalEdge_NilThreatDetectionConfigHasNoRunnableDetection verifies that
// when threat-detection is explicitly disabled (nil config), the system correctly
// identifies no runnable detection — preventing accidental audit-trail gaps.
func TestFormalEdge_NilThreatDetectionConfigHasNoRunnableDetection(t *testing.T) {
var td *workflow.ThreatDetectionConfig
// nil pointer — detection was explicitly disabled (threat-detection: false in YAML).
assert.Nil(t, td, "edge case: explicit disable must yield nil ThreatDetectionConfig")
}
// TestFormalEdge_EmptyNetworkBlockedListPermitsAllowed verifies the edge case
// where allowed domains are non-empty but the blocked list is nil/empty — all
// allowed domains must remain accessible.
func TestFormalEdge_EmptyNetworkBlockedListPermitsAllowed(t *testing.T) {
network := workflow.NetworkPermissions{
Allowed: []string{"api.github.com", "registry.npmjs.org"},
Blocked: nil,
}
for _, domain := range network.Allowed {
inBlocked := false
for _, b := range network.Blocked {
if b == domain {
inBlocked = true
}
}
assert.False(t, inBlocked,
"edge case: allowed domain %q must not appear in empty blocked list", domain)
}
}
// TestFormalEdge_ConditionalThreatDetectionIsConditional verifies that when
// EnabledExpr is set on a ThreatDetectionConfig, IsConditional() returns true
// and the detection job is always compiled but conditionally executed at runtime.
func TestFormalEdge_ConditionalThreatDetectionIsConditional(t *testing.T) {
expr := "${{ inputs.enable-threat-detection }}"
td := &workflow.ThreatDetectionConfig{
EnabledExpr: &expr,
}
assert.True(t, td.IsConditional(),
"edge case: ThreatDetectionConfig with EnabledExpr must report IsConditional() = true")
assert.True(t, td.HasRunnableDetection(),
"edge case: conditional detection config must always be considered runnable at compile time")
}
// ---------------------------------------------------------------------------
// Test helpers
// ---------------------------------------------------------------------------
func boolPtr(b bool) *bool { return &b }
func templatableBoolTrue() *workflow.TemplatableBool {
b := true
return &workflow.TemplatableBool{Value: &b}
}
func templatableBoolFalse() *workflow.TemplatableBool {
b := false
return &workflow.TemplatableBool{Value: &b}
}
Summary
specs/security-architecture-spec-summary.mddescribes the GitHub Agentic Workflows (gh-aw) security architecture: a 7-layer defense-in-depth model enforcing seven formal security guarantees (SG-01 through SG-07) covering input sanitization, output isolation, network access control, least-privilege permissions, sandbox isolation, auditability, and fail-secure behavior. This formalization models the architecture as a state machine with invariants drawn from the W3C-style conformance classes (Basic / Standard / Complete), the RFC 2119 requirements across Sections 4–11, and the job pipeline topology (pre_activation → activation → agent → detection → safe_outputs → conclusion). Fifteen predicates are derived and mapped to Go testify unit tests covering all guarantees and edge cases.Specification
specs/security-architecture-spec-summary.mdFormal Model
Predicates and invariants (illustrative notation)
Behavioral Coverage Map
SG01_InputSanitizationTestFormalSG01_InputSanitizationInvariantSG02_AgentReadOnlyTestFormalSG02_AgentJobHasNoWritePermissionsSG03_NetworkAllowlistTestFormalSG03_NetworkAllowlistEnforcementSG04_LeastPrivilegeTestFormalSG04_LeastPrivilegeBasePermissionsSG05_SandboxIsolationTestFormalSG05_SandboxIsolationPresenceSG06_AuditabilityTestFormalSG06_ThreatDetectionAuditArtifactSG07_FailSecureTestFormalSG07_FailSecureOnSecurityErrorBasicConformanceTestFormalBasicConformance_AllFourControlsThreatDetectionOrDefaultTestFormalThreatDetection_EnabledByDefaultThreatDetectionOrDefaultTestFormalThreatDetection_ExplicitDisableIsContinueOnErrorTestFormalThreatDetection_ContinueOnErrorDefaultStagedHandlerNoWritePermsTestFormalStaged_HandlerRequiresNoWritePermsIDTokenRequirementTestFormalIDToken_OIDCVaultActionsRequireWriteScopegetPushFallbackAsPullRequestTestFormalPushFallback_DefaultsToTrueJobTopologyOrderTestFormalJobTopology_PipelineOrderEnforcedGenerated Test Suite
📄 `pkg/workflow/security_architecture_formal_test.go`
Usage
pkg/workflow/security_architecture_formal_test.go.//go:linknamesparingly):IsHandlerStaged(globalStaged bool, handlerStaged *TemplatableBool) boolStepsRequireIDToken(steps []any) boolGetPushFallbackAsPullRequest(config *PushToPullRequestBranchConfig) boolNewCompilerWithOptions(opts ...CompilerOption) *CompilerNewPermissionsFromMap(m map[PermissionScope]PermissionLevel) *Permissions// stubinterfaces with real implementations once exports are wired.go test ./pkg/workflow/... -run FormalContext
specs/security-architecture-spec-summary.md