diff --git a/.github/workflows/smoke-call-workflow.lock.yml b/.github/workflows/smoke-call-workflow.lock.yml index 15a3ed21b8c..eacad788ec7 100644 --- a/.github/workflows/smoke-call-workflow.lock.yml +++ b/.github/workflows/smoke-call-workflow.lock.yml @@ -619,6 +619,11 @@ jobs: "inputSchema": { "additionalProperties": false, "properties": { + "aw_context": { + "default": "", + "description": "Agent caller context (used internally by Agentic Workflows).", + "type": "string" + }, "payload": { "description": "Input parameter 'payload' for workflow smoke-workflow-call", "type": "string" @@ -1104,15 +1109,25 @@ jobs: needs: safe_outputs if: needs.safe_outputs.outputs.call_workflow_name == 'smoke-workflow-call' # Imported from called workflow "smoke-workflow-call" because GitHub requires the caller job to grant permissions requested by reusable workflow jobs. - # Review the called workflow's frontmatter permissions in ./.github/workflows/smoke-workflow-call.md. + # Review the called workflow's job-level permissions in ./.github/workflows/smoke-workflow-call.lock.yml. permissions: + actions: read contents: read - pull-requests: read + issues: write + pull-requests: write uses: ./.github/workflows/smoke-workflow-call.lock.yml with: + aw_context: ${{ fromJSON(needs.safe_outputs.outputs.call_workflow_payload).aw_context }} payload: ${{ needs.safe_outputs.outputs.call_workflow_payload }} task-description: ${{ fromJSON(needs.safe_outputs.outputs.call_workflow_payload)['task-description'] }} - secrets: inherit + secrets: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + GH_AW_OTEL_GRAFANA_AUTHORIZATION: ${{ secrets.GH_AW_OTEL_GRAFANA_AUTHORIZATION }} + GH_AW_OTEL_GRAFANA_ENDPOINT: ${{ secrets.GH_AW_OTEL_GRAFANA_ENDPOINT }} + GH_AW_OTEL_SENTRY_AUTHORIZATION: ${{ secrets.GH_AW_OTEL_SENTRY_AUTHORIZATION }} + GH_AW_OTEL_SENTRY_ENDPOINT: ${{ secrets.GH_AW_OTEL_SENTRY_ENDPOINT }} conclusion: needs: diff --git a/pkg/cli/audit.go b/pkg/cli/audit.go index 965a70e38fc..20691c759f7 100644 --- a/pkg/cli/audit.go +++ b/pkg/cli/audit.go @@ -326,6 +326,13 @@ type auditRunConfig struct { evalsArtifactRequested bool } +// auditAnalysisResults is populated concurrently during audit collection. +// Each field is written by exactly one goroutine (or one launch* helper that +// exclusively owns a disjoint field set) before collectAuditAnalysisResults +// waits on the shared WaitGroup and reads the aggregate result. +// +// Keep launch* helper field ownership disjoint: sharing a field between +// goroutines without adding synchronization would introduce a data race. type auditAnalysisResults struct { metrics LogMetrics failedJobCount int @@ -665,6 +672,7 @@ func collectAuditAnalysisResults(run WorkflowRun, runOutputDir string, verbose b return results } +// launchCoreAuditAnalyses exclusively writes missingTools, missingData, noops, mcpFailures, and accessAnalysis. func launchCoreAuditAnalyses(wg *sync.WaitGroup, results *auditAnalysisResults, run WorkflowRun, runOutputDir string, verbose bool) { // Resolve experiment assignment once so all goroutines reuse the same values // rather than each reading state.json independently. @@ -699,6 +707,7 @@ func launchCoreAuditAnalyses(wg *sync.WaitGroup, results *auditAnalysisResults, }) } +// launchMetricsAnalysis exclusively writes results.metrics. func launchMetricsAnalysis(wg *sync.WaitGroup, results *auditAnalysisResults, runOutputDir string, verbose bool, workflowPath string) { wg.Go(func() { metrics, err := extractLogMetrics(runOutputDir, verbose, workflowPath) @@ -713,6 +722,7 @@ func launchMetricsAnalysis(wg *sync.WaitGroup, results *auditAnalysisResults, ru }) } +// launchJobDetailsAnalysis exclusively writes results.jobDetails and results.failedJobCount. func launchJobDetailsAnalysis(wg *sync.WaitGroup, results *auditAnalysisResults, runID int64, verbose bool) { wg.Go(func() { jobDetails, failedJobCount, err := fetchJobDetailsWithCounts(runID, verbose) @@ -728,6 +738,7 @@ func launchJobDetailsAnalysis(wg *sync.WaitGroup, results *auditAnalysisResults, }) } +// launchFirewallAuditAnalyses exclusively writes policyAnalysis, mcpToolUsage, and tokenUsageSummary. func launchFirewallAuditAnalyses(wg *sync.WaitGroup, results *auditAnalysisResults, runOutputDir string, verbose bool) { launchFirewallAnalysis(wg, results, runOutputDir, verbose) runAuditAnalysis(wg, verbose, "analyzeFirewallPolicy", "Failed to analyze firewall policy", func(v *PolicyAnalysis) { @@ -747,6 +758,7 @@ func launchFirewallAuditAnalyses(wg *sync.WaitGroup, results *auditAnalysisResul }) } +// launchFirewallAnalysis exclusively writes results.firewallAnalysis. func launchFirewallAnalysis(wg *sync.WaitGroup, results *auditAnalysisResults, runOutputDir string, verbose bool) { wg.Go(func() { firewallAnalysis, err := analyzeFirewallLogs(runOutputDir, verbose) @@ -767,6 +779,7 @@ func launchFirewallAnalysis(wg *sync.WaitGroup, results *auditAnalysisResults, r }) } +// launchSupplementalAuditAnalyses exclusively writes redactedDomainsAnalysis, rateLimitUsage, artifacts, and safeItemsCount. func launchSupplementalAuditAnalyses(wg *sync.WaitGroup, results *auditAnalysisResults, runOutputDir string, verbose bool) { runAuditAnalysis(wg, verbose, "analyzeRedactedDomains", "Failed to analyze redacted domains", func(v *RedactedDomainsAnalysis) { results.redactedDomainsAnalysis = v