Skip to content

feat: support github-app in max-daily-ai-credits for dedicated guardrail token#43508

Open
pelikhan with Copilot wants to merge 4 commits into
mainfrom
copilot/add-github-app-support-for-credits
Open

feat: support github-app in max-daily-ai-credits for dedicated guardrail token#43508
pelikhan with Copilot wants to merge 4 commits into
mainfrom
copilot/add-github-app-support-for-credits

Conversation

Copilot AI commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

The max-daily-ai-credits guardrail reuses the activation token (or GITHUB_TOKEN) for its API calls, which depletes credits from the primary app. This adds an object form that lets you specify a dedicated GitHub App for those calls.

New syntax

max-daily-ai-credits:
  value: 10000
  github-app:
    client-id: ${{ vars.AIC_APP_CLIENT_ID }}
    private-key: ${{ secrets.AIC_APP_PRIVATE_KEY }}

The scalar form (max-daily-ai-credits: 10000) is unchanged.

Changes

  • daily_aic_workflow.goextractMaxDailyAICObjectValue unwraps value from the object form; extractMaxDailyAICGitHubApp extracts the app config. isMaxDailyAICDisabled, resolveMaxDailyAICFromRaw, and validateMaxDailyAICFrontmatter updated to handle the object form.
  • workflow_data.go — New MaxDailyAICreditsGitHubApp *GitHubAppConfig field.
  • workflow_builder.go — Populates MaxDailyAICreditsGitHubApp from frontmatter.
  • compiler_activation_job_builder.gobuildDailyAICAppTokenMintStep emits a daily-aic-app-token step (gated on GH_AW_MAX_DAILY_AI_CREDITS != '', requests actions: read). resolveDailyAICToken uses the dedicated token when configured, falling back to the activation token.
  • main_workflow_schema.json — Object form (required value, optional github-app) added to max_daily_ai_credits_limit.

Copilot AI and others added 2 commits July 5, 2026 07:45
Add support for specifying a dedicated GitHub App in the
max-daily-ai-credits frontmatter field via an object form:

  max-daily-ai-credits:
    value: 123
    github-app:
      client-id: ${{ vars.APP_ID }}
      private-key: ${{ secrets.APP_PRIVATE_KEY }}

The github-app is used to mint a dedicated token for the daily AIC
guardrail API calls, avoiding depletion of credits from other apps.

Changes:
- daily_aic_workflow.go: parse object form, extract github-app config
- workflow_data.go: add MaxDailyAICreditsGitHubApp field
- workflow_builder.go: populate MaxDailyAICreditsGitHubApp
- compiler_activation_job_builder.go: mint token and use in guardrail
- main_workflow_schema.json: add object form to schema
- daily_aic_workflow_guardrail_test.go: add tests for new functionality

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…value key

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title feat: add github-app support for max-daily-ai-credits guardrail feat: support github-app in max-daily-ai-credits for dedicated guardrail token Jul 5, 2026
Copilot AI requested a review from pelikhan July 5, 2026 07:50
@pelikhan pelikhan marked this pull request as ready for review July 5, 2026 08:06
Copilot AI review requested due to automatic review settings July 5, 2026 08:06
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

⚠️ PR Code Quality Reviewer failed during code quality review.

@github-actions

github-actions Bot commented Jul 5, 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 support for configuring a dedicated GitHub App token for the max-daily-ai-credits guardrail, so its GitHub API calls don’t consume credits from the activation app token (or default GITHUB_TOKEN). This extends frontmatter to accept an object form that includes both the credit limit and an optional github-app token-mint configuration.

Changes:

  • Add object-form parsing for max-daily-ai-credits and extract an optional github-app config.
  • Extend WorkflowData / builder wiring to carry the dedicated app config through compilation.
  • Emit an activation-job token-mint step for the daily AIC guardrail and use its token for guardrail API calls; update schema + add tests.
Show a summary per file
File Description
pkg/workflow/workflow_data.go Adds MaxDailyAICreditsGitHubApp to carry the optional dedicated app config.
pkg/workflow/workflow_builder.go Populates MaxDailyAICreditsGitHubApp from frontmatter during workflow data construction.
pkg/workflow/daily_aic_workflow.go Normalizes object-form max-daily-ai-credits by extracting value; adds helper to extract github-app.
pkg/workflow/daily_aic_workflow_guardrail_test.go Adds coverage for object-form parsing/extraction and compiled workflow output expectations.
pkg/workflow/compiler_activation_job_builder.go Adds daily-AIC app token mint step + token resolution to use dedicated token for guardrail steps.
pkg/parser/schemas/main_workflow_schema.json Extends schema to allow max-daily-ai-credits object form with required value and optional github-app.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 6/6 changed files
  • Comments generated: 3
  • Review effort level: Low

Comment on lines +394 to +396
func (c *Compiler) buildDailyAICAppTokenMintStep(app *GitHubAppConfig, data *WorkflowData) []string {
var steps []string
steps = append(steps, " - name: Generate GitHub App token for daily AIC guardrail\n")
Comment on lines +423 to +429
if data.MaxDailyAICreditsGitHubApp != nil {
if data.MaxDailyAICreditsGitHubApp.shouldIgnoreMissingKey() {
return combineTokenExpressions(
fmt.Sprintf("${{ steps.%s.outputs.token }}", dailyAICAppTokenStepID),
"${{ secrets.GITHUB_TOKEN }}",
)
}
Comment on lines +11881 to +11884
"value": {
"$ref": "#/$defs/positive_ai_credits_limit",
"description": "The maximum AI Credits budget (positive integer, K/M suffix, or expression)."
},
@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate — ADR Required

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

Draft ADR committed: docs/adr/43508-github-app-dedicated-token-for-daily-aic-guardrail.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 (e.g., whether the primary motivation is cost, security posture, or operational isolation), 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-43508: Dedicated GitHub App Token for Daily AI Credits Guardrail](docs/adr/43508-github-app-dedicated-token-for-daily-aic-guardrail.md)

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

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · 61.8 AIC · ⌖ 10.3 AIC · ⊞ 8.4K ·
Comment /review to run again

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 88/100 — Excellent

Analyzed 17 test scenario(s): 17 design, 0 implementation, 0 violation(s).

📊 Metrics (17 tests)
Metric Value
Analyzed 17 (Go: 17, JS: 0)
✅ Design 17 (100%)
⚠️ Implementation 0 (0%)
Edge/error coverage 10 (59%)
Duplicate clusters 0
Inflation No (see note)
🚨 Violations 0

Build tag: ✅ //go:build !integration on line 1
Go mocking violations: ✅ None

Test File Classification Issues
TestResolveMaxDailyAIC (8 subtests) daily_aic_workflow_guardrail_test.go:16 ✅ design_test / behavioral_contract None
TestDailyAICWorkflowGuardrailInCompiledWorkflow daily_aic_workflow_guardrail_test.go:85 ✅ design_test / behavioral_contract None
TestDailyETGuardrailDynamicGate daily_aic_workflow_guardrail_test.go:191 ✅ design_test / behavioral_contract None
TestDailyAICWorkflowGuardrailConfiguredViaEnvVar daily_aic_workflow_guardrail_test.go:236 ✅ design_test / behavioral_contract None
TestDailyETGuardrailNegativeValueRejected daily_aic_workflow_guardrail_test.go:280 ✅ design_test / behavioral_contract None
TestMaxDailyAICObjectForm (4 subtests) daily_aic_workflow_guardrail_test.go:313 ✅ design_test / behavioral_contract None
TestMaxDailyAICWithGitHubAppCompiledWorkflow daily_aic_workflow_guardrail_test.go:370 ✅ design_test / behavioral_contract None

Inflation note: The test file adds 115 lines vs 57 in daily_aic_workflow.go (2.02:1 raw ratio). However, the tests also exercise compiler_activation_job_builder.go (+58 lines), making the effective ratio approximately 1:1 across all tested production code. Not flagged as inflation.

Highlights

TestResolveMaxDailyAIC — 8 subtests covering the full resolution priority chain: literal frontmatter, imported expressions, runtime defaults, suffix normalization (100M100000000), explicit disable sentinel (-1), and spec §9.3(2) import-vs-frontmatter precedence. Strong behavioral coverage with well-described failure messages.

TestDailyAICWorkflowGuardrailInCompiledWorkflow — Snapshot-style test with 18 assertions against the compiled lock file, including a negative assertion that issues: write is absent from activation permissions. Verifies the full compiled output contract.

TestDailyETGuardrailNegativeValueRejected — Validates that -2 (below the minimum sentinel -1) is rejected at compile time with the correct error message. Good error-path coverage.

TestMaxDailyAICWithGitHubAppCompiledWorkflow — Exercises the new GitHub App object-form feature: verifies token mint step ID, client-id/private-key passthrough, actions: read permission, token reference propagation, and threshold normalization in the compiled output.

Verdict

Passed. 0% implementation tests (threshold: 30%). No guideline violations. All 17 test scenarios verify behavioral contracts against compiled output or resolution invariants.

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 · 52.3 AIC · ⌖ 10.9 AIC · ⊞ 6.8K ·
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: 88/100 — Excellent. 0% implementation tests (threshold: 30%). No guideline violations. All 17 test scenarios verify behavioral contracts.

@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: feat: support github-app in max-daily-ai-credits for dedicated guardrail token

The overall approach is clean and well-structured — the scalar fallback is preserved, the JSON schema is updated, and the tests cover the main happy paths. Two correctness issues need fixing before merge; one code-clarity issue is a follow-up.

Blocking

  1. repositories field is hardcoded in the mint step (compiler_activation_job_builder.go:412)
    buildDailyAICAppTokenMintStep always emits repositories: ${{ github.event.repository.name }}, ignoring app.Repositories. The analogous buildPreActivationAppTokenMintStep handles single-repo, multi-repo, and org-wide (*) cases correctly. A user who configures repositories: on the AIC github-app will get a token with the wrong scope silently.

  2. app.Permissions is never forwarded (compiler_activation_job_builder.go:415)
    Only permission-actions: read is emitted, ignoring any permissions: keys the user may have set on the GitHubAppConfig. Either enforce that permissions: is not allowed on the AIC app config (with a validation error), or forward them consistently with the rest of the infrastructure.

Non-blocking

  1. Double extractMaxDailyAICObjectValue call (daily_aic_workflow.go:115)
    In resolveMaxDailyAICFromRaw, effective is already unwrapped, then passed to isMaxDailyAICDisabled(effective) which extracts again. Benign today but noisy and fragile.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 101.9 AIC · ⌖ 6.38 AIC · ⊞ 4.9K

owner = "${{ github.repository_owner }}"
}
steps = append(steps, fmt.Sprintf(" owner: %s\n", owner))
steps = append(steps, " repositories: ${{ github.event.repository.name }}\n")

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.

The repositories field is hardcoded to ${{ github.event.repository.name }} and ignores the user-configured app.Repositories values. Compare to buildPreActivationAppTokenMintStep (compiler_pre_activation_job.go:665–675) which correctly handles single-repo, multi-repo, and org-wide (*) cases.

If a user supplies a non-default repository scope in their github-app: config, the minted token will silently ignore it. The same handling from the pre-activation step should be applied here:

if len(app.Repositories) == 1 && app.Repositories[0] == "*" {
    // org-wide: omit repositories field
} else if len(app.Repositories) == 1 {
    steps = append(steps, fmt.Sprintf("          repositories: %s\n", app.Repositories[0]))
} else if len(app.Repositories) > 1 {
    steps = append(steps, "          repositories: |-\n")
    for _, repo := range app.Repositories {
        steps = append(steps, fmt.Sprintf("            %s\n", repo))
    }
} else {
    steps = append(steps, "          repositories: ${{ github.event.repository.name }}\n")
}

@copilot please address this.

steps = append(steps, " repositories: ${{ github.event.repository.name }}\n")
steps = append(steps, " github-api-url: ${{ github.api_url }}\n")
// The guardrail script reads workflow run data, so actions: read is required.
steps = append(steps, " permission-actions: read\n")

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.

The mint step hardcodes only permission-actions: read and ignores the app.Permissions map that users may configure on the GitHubAppConfig struct. The other mint steps (e.g. the GitHub MCP step) forward extra permissions from the config. If this is intentional (guardrail only ever needs actions:read), that constraint should be enforced at parse time by rejecting permissions: on the AIC app config. Otherwise, the extra permissions should be merged here to be consistent with the rest of the app-token infrastructure.

@copilot please address this.

func resolveMaxDailyAICFromRaw(raw any) (*string, bool) {
if isMaxDailyAICDisabled(raw) {
effective := extractMaxDailyAICObjectValue(raw)
if isMaxDailyAICDisabled(effective) {

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.

Double extraction: effective was already obtained by extractMaxDailyAICObjectValue(raw) on line 114, but it is then passed to isMaxDailyAICDisabled(effective) which calls extractMaxDailyAICObjectValue(effective) again internally. This is benign today (the second call is a no-op because effective is already a scalar), but it creates a confusing read and would silently misbehave if the logic ever changes.

Either have isMaxDailyAICDisabled accept a pre-unwrapped value and skip the internal extraction, or inline the check here and pass the already-unwrapped effective to a simpler helper.

@copilot please address this.

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Review Summary — PR #43508

Applied /tdd and /codebase-design skills. Requesting changes on 2 correctness issues and 3 test coverage gaps.

🔍 Issues found (5)
# File Issue Skill
1 compiler_activation_job_builder.go:412 repositories field on the github-app object is silently ignored; hardcoded to current repo /tdd
2 compiler_activation_job_builder.go:394 Unused data *WorkflowData parameter in buildDailyAICAppTokenMintStep /codebase-design
3 daily_aic_workflow.go:63 ignore-if-missing path in resolveDailyAICToken is unreachable because extractMaxDailyAICGitHubApp returns nil for empty AppID/PrivateKey; no test covers this /tdd
4 compiler_activation_job_builder.go:415 permission-actions: read hardcoded; bypasses app.Permissions merge, ignoring user-supplied overrides /codebase-design
5 daily_aic_workflow_guardrail_test.go Missing tests: object-form -1 (disabled), mint step if: condition assertion, and ignore-if-missing combined with object form /tdd
✅ What's working well
  • Backward-compatible design (scalar form unchanged)
  • extractMaxDailyAICObjectValue cleanly normalises both forms at all three call-sites
  • maxDailyAICreditsConfiguredIfExpr guard on the mint step
  • End-to-end compiled-workflow test
  • Named dailyAICAppTokenStepID constant

@copilot please address the review comments above.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 137.1 AIC · ⌖ 8.81 AIC · ⊞ 6.6K ·
Comment /matt 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.

Skills-Based Review 🧠

Applied /tdd and /codebase-design — requesting changes on a few correctness and coverage gaps before merge.

📋 Key Themes & Highlights

Key Themes

  • repositories field silently ignored (correctness): buildDailyAICAppTokenMintStep hardcodes ${{ github.event.repository.name }} and never consults app.Repositories, unlike every other mint step in the codebase.
  • ignore-if-missing path is unreachable (correctness + /tdd): extractMaxDailyAICGitHubApp returns nil whenever AppID or PrivateKey is empty, but resolveDailyAICToken has a dedicated ignore-if-missing fallback branch that can therefore never trigger.
  • Test coverage gaps (/tdd): No test for object-form -1 (disabled), no assertion on the mint step if: condition, and no test for ignore-if-missing combined with the object form.
  • Unused data parameter (/codebase-design): buildDailyAICAppTokenMintStep(app, data) never reads data, adding noise to the signature.
  • Hardcoded permission-actions: read (/codebase-design): The baseline permission is necessary but bypasses the normal app.Permissions merge path, silently discarding any user-supplied permission overrides.

Positive Highlights

  • ✅ Clean backward-compatible design: scalar form continues to work unchanged
  • extractMaxDailyAICObjectValue nicely normalises both forms with a single helper used consistently across all three affected callsites
  • ✅ Good use of the maxDailyAICreditsConfiguredIfExpr guard to ensure the mint step skips when the guardrail is inactive
  • ✅ Solid end-to-end test (TestMaxDailyAICWithGitHubAppCompiledWorkflow) that compiles a real workflow and inspects the lock file output
  • ✅ Step ID extracted as a named constant (dailyAICAppTokenStepID), making future refactoring safe

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 137.1 AIC · ⌖ 8.81 AIC · ⊞ 6.6K
Comment /matt to run again

owner = "${{ github.repository_owner }}"
}
steps = append(steps, fmt.Sprintf(" owner: %s\n", owner))
steps = append(steps, " repositories: ${{ github.event.repository.name }}\n")

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 repositories field configured on the github-app object is silently ignored — buildPreActivationAppTokenMintStep honours app.Repositories with a full multi-value path, but this step always emits the hardcoded current-repo value, creating a surprising behavioural gap. A test asserting that a configured repositories list appears in the compiled output would surface this.

💡 Suggested fix

Replace the single hardcoded line with the same repository-resolution logic used in buildPreActivationAppTokenMintStep:

if len(app.Repositories) == 1 && app.Repositories[0] == "*" {
    // org-wide: omit repositories field
} else if len(app.Repositories) == 1 {
    steps = append(steps, fmt.Sprintf("          repositories: %s\n", app.Repositories[0]))
} else if len(app.Repositories) > 1 {
    steps = append(steps, "          repositories: |-\n")
    for _, repo := range app.Repositories {
        steps = append(steps, fmt.Sprintf("            %s\n", repo))
    }
} else {
    steps = append(steps, "          repositories: ${{ github.event.repository.name }}\n")
}

@copilot please address this.

//
// The step is gated on maxDailyAICreditsConfiguredIfExpr so it is skipped when
// the guardrail is not active at runtime.
func (c *Compiler) buildDailyAICAppTokenMintStep(app *GitHubAppConfig, data *WorkflowData) []string {

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.

[/codebase-design] The data *WorkflowData parameter is accepted but never read inside this function — only app is used. This creates dead weight in the signature and makes the function's dependencies harder to understand at a glance.

💡 Suggested change

Drop the unused parameter:

func (c *Compiler) buildDailyAICAppTokenMintStep(app *GitHubAppConfig) []string {

Update the single call-site accordingly:

steps = append(steps, c.buildDailyAICAppTokenMintStep(data.MaxDailyAICreditsGitHubApp)...)

Keeping signatures minimal makes the function easier to test in isolation (no WorkflowData scaffolding needed).

@copilot please address this.

return nil
}
app := parseAppConfig(appMap)
if app.AppID == "" || app.PrivateKey == "" {

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] An app with ignore-if-missing: true but empty AppID/PrivateKey is silently discarded here. But resolveDailyAICToken specifically has an ignore-if-missing branch (combineTokenExpressions) that can never be reached from this extractor, since it always returns nil for such configs. There is no test covering this code path.

💡 Suggested test and design note
t.Run("object form with ignore-if-missing is preserved", func(t *testing.T) {
    frontmatter := map[string]any{
        "max-daily-ai-credits": map[string]any{
            "value": 5000,
            "github-app": map[string]any{
                "client-id":         "${{ vars.APP_ID }}",
                "private-key":       "${{ secrets.APP_KEY }}",
                "ignore-if-missing": true,
            },
        },
    }
    app := extractMaxDailyAICGitHubApp(frontmatter)
    if app == nil {
        t.Fatal("expected non-nil app when ignore-if-missing is set")
    }
    if !app.IgnoreIfMissing {
        t.Fatal("expected IgnoreIfMissing to be true")
    }
})

Consider whether the guard on line 63 (app.AppID == "" || app.PrivateKey == "") should be relaxed to allow ignore-if-missing: true with empty fields, mirroring how other app configs behave.

@copilot please address this.

steps = append(steps, " repositories: ${{ github.event.repository.name }}\n")
steps = append(steps, " github-api-url: ${{ github.api_url }}\n")
// The guardrail script reads workflow run data, so actions: read is required.
steps = append(steps, " permission-actions: read\n")

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.

[/codebase-design] The permission-actions: read permission is hardcoded here rather than derived from app.Permissions, which is the standard pattern for all other app token mint steps. This means users cannot extend or override permissions for the AIC guardrail app token (e.g., to add workflows: read if their guardrail script needs it in future).

💡 Suggested improvement

Derive permissions from app.Permissions merged with the guardrail-required baseline, consistent with how buildPreActivationAppTokenMintStep handles this via the shared permissions mechanism. At minimum, document why AIC-specific permissions are fixed at this value so future maintainers don't inadvertently remove the constraint.

@copilot please address this.

})
}

func TestMaxDailyAICWithGitHubAppCompiledWorkflow(t *testing.T) {

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] TestMaxDailyAICWithGitHubAppCompiledWorkflow verifies that the mint step appears in the compiled output, but doesn't verify the if: condition on that step. If the conditional (GH_AW_MAX_DAILY_AI_CREDITS != '') is wrong or absent, the token step would run unconditionally and fail in repos that don't set the guardrail — a regression this test would miss.

💡 Suggested assertion
// Verify the mint step is gated on the guardrail env var being set
if !strings.Contains(lockStr, "if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }}") {
    t.Fatal("expected mint step to be gated on guardrail env var")
}

@copilot please address this.

}
}

func TestMaxDailyAICObjectForm(t *testing.T) {

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] There's no test for the object form disabled case (value: -1 in object form). The scalar isMaxDailyAICDisabled was updated to go through extractMaxDailyAICObjectValue, but there's no regression guard for the object form of -1.

💡 Suggested test
t.Run("object form with value -1 is treated as disabled", func(t *testing.T) {
    got := resolveMaxDailyAIC(map[string]any{
        "max-daily-ai-credits": map[string]any{
            "value": -1,
        },
    }, "")
    if got != nil {
        t.Fatalf("expected nil (disabled) for object form value -1, got %v", got)
    }
})

@copilot please address this.

// and returned. When the object form lacks a "value" key, nil is returned so
// that downstream callers treat the limit as unset and fall back to imported or
// default values. When the value is not in object form, it is returned unchanged.
func extractMaxDailyAICObjectValue(raw any) any {

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.

[/codebase-design] extractMaxDailyAICObjectValue returns nil when the raw value is an object without a value key — but callers like parseMaxDailyAICValue and isMaxDailyAICDisabled receive nil and silently treat the limit as unset. This means a malformed object form ({github-app: {...}} with no value) produces no validation error and no guardrail, which may surprise users.

💡 Suggestion

Consider returning a sentinel or threading a validation error up through validateMaxDailyAICFrontmatter when the object form is present but missing value. The JSON schema already enforces required: ["value"], but runtime parsing is separate. A clear error like "max-daily-ai-credits object form requires a 'value' field" would be more actionable than silent no-op.

@copilot please address this.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, review the remaining feedback, ensure the branch is current, and rerun checks.

Generated by 👨‍🍳 PR Sous Chef · 16.1 AIC · ⌖ 6.23 AIC · ⊞ 4.6K ·
Comment /souschef to run again

…ription

- Remove unused `data *WorkflowData` parameter from buildDailyAICAppTokenMintStep
- Fix resolveDailyAICToken ignore-if-missing fallback to use resolveActivationToken
  instead of hardcoded ${{ secrets.GITHUB_TOKEN }}, consistent with the function's
  documented behaviour
- Clarify schema description for object-form `value` to state that disabling via
  -1 requires the scalar form

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 5, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, review the remaining feedback, ensure the branch is current, and rerun checks.

...

Addressed the three review issues in commit fix: remove unused parameter, fix fallback token, clarify schema description:

  1. Unused parameter — removed data *WorkflowData from buildDailyAICAppTokenMintStep and updated the call site.
  2. Wrong fallback tokenresolveDailyAICToken's ignore-if-missing path now falls back to c.resolveActivationToken(data) instead of the hardcoded ${{ secrets.GITHUB_TOKEN }}, which correctly honours any configured activation GitHub App or custom token.
  3. Schema -1 ambiguity — updated the object-form value description to state that disabling via -1 requires the scalar form; the schema already restricts value to positive integers so this is now clearly documented.

Local make fmt, make test-unit (pkg/workflow passes; pre-existing TestPRSousChefWorkflowAddCommentTargetContract failure in pkg/cli is unrelated). CI needs a human re-trigger to confirm.

Copilot AI requested a review from gh-aw-bot July 5, 2026 09:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants