Skip to content

fix(#2528): sync VALID_ROLES with Go's ValidRoles() - #6369

Closed
shairevivo wants to merge 4 commits into
fullsend-ai:mainfrom
shairevivo:srevivo/fix-2528-sync-valid-roles
Closed

fix(#2528): sync VALID_ROLES with Go's ValidRoles()#6369
shairevivo wants to merge 4 commits into
fullsend-ai:mainfrom
shairevivo:srevivo/fix-2528-sync-valid-roles

Conversation

@shairevivo

Copy link
Copy Markdown
Contributor

Summary

  • Add missing fix, retro, prioritize, and e2e roles to VALID_ROLES in web/admin/src/lib/layers/orgConfigParse.ts, matching Go's ValidRoles()
  • Update both error messages to list all 8 valid roles
  • Add parameterized test covering all 8 roles to prevent future drift

Closes #2528

Test plan

  • vitest run orgConfigParse.test.ts — 18/18 pass (8 parameterized role tests + 10 existing)
  • Live browser verification via Vite dev server console: all 8 roles return VALID, invalid role correctly rejected

🤖 Generated with Claude Code

The web admin's VALID_ROLES had only 4 roles while the backend accepts
8. Add the missing fix, retro, prioritize, and e2e roles and update
both error messages. A parameterized test now covers all 8 roles to
prevent future drift.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Shai Revivo <srevivo@redhat.com>
@shairevivo
shairevivo requested a review from a team as a code owner August 19, 2026 09:03
@github-actions

Copy link
Copy Markdown

E2E tests did not run

E2E tests run automatically for org/repo members and collaborators on pull requests.

For other contributors, a maintainer must add the ok-to-test label after the latest push.

See E2E testing guide for details.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Sync web admin role validation with Go backend

🐞 Bug fix 🧪 Tests 🕐 Less than 10 minutes

Grey Divider

AI Description

• Accept all eight roles supported by the Go configuration validator.
• Report the complete role set in validation errors.
• Test every supported role across defaults and agent configuration.
Diagram

graph TD
  A["Org YAML"] --> B["YAML Parser"] --> C["Role Validator"] --> D["Validation Result"]
  E["Go ValidRoles"] -. "role parity" .-> F["Web Role Set"] --> C
  G["Role Matrix Tests"] --> C
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Generate roles from a canonical manifest
  • ➕ Prevents frontend and backend role lists from drifting
  • ➕ Allows both implementations and tests to consume one contract
  • ➖ Introduces generation or packaging infrastructure
  • ➖ Disproportionate complexity for this focused fix
2. Delegate validation to the backend
  • ➕ Keeps role policy exclusively in Go
  • ➕ Eliminates duplicated frontend policy
  • ➖ Requires a network-dependent validation flow
  • ➖ Reduces immediate client-side feedback and offline behavior

Recommendation: The targeted synchronization is the best short-term fix because it is low risk and restores backend parity immediately. If roles change frequently, follow up with a shared generated manifest; the parameterized test still duplicates the role list and cannot detect future roles added only in Go.

Files changed (2) +19 / -3

Bug fix (1) +3 / -3
orgConfigParse.tsAlign frontend valid roles with Go +3/-3

Align frontend valid roles with Go

• Adds fix, retro, prioritize, and e2e to the web admin role set. Both default-role and agent-role validation errors now enumerate all eight accepted values.

web/admin/src/lib/layers/orgConfigParse.ts

Tests (1) +16 / -0
orgConfigParse.test.tsCover every backend-supported role +16/-0

Cover every backend-supported role

• Adds a parameterized test that validates all eight roles in both default-role and agent-role positions. This provides regression coverage for the expanded frontend role set.

web/admin/src/lib/layers/orgConfigParse.test.ts

@qodo-code-review

qodo-code-review Bot commented Aug 19, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Legacy agent entries accepted ✓ Resolved 🐞 Bug ≡ Correctness
Description
Expanding the shared VALID_ROLES set makes validateOrgConfig accept newly added roles such as
fix or e2e in legacy agents[].role entries, although Go rejects any role-based agent entry
without a source. The admin config layer consequently reports these unusable configurations as
installed and valid.
Code

web/admin/src/lib/layers/orgConfigParse.ts[16]

+const VALID_ROLES = new Set(["fullsend", "triage", "coder", "review", "fix", "retro", "prioritize", "e2e"]);
Relevance

●● Moderate

PR fixes the intended VALID_ROLES sync; deeper agent-source mismatch is a separate, more
architectural concern not directly requested.

PR-#617

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The frontend type and validator model agents as role entries and the changed test proves newly added
roles return valid. In contrast, Go's AgentEntry contains source/name/enabled fields and its YAML
unmarshaller explicitly rejects role mappings without a source; analyzeConfigRepoLayer labels any
frontend validation success as installed and valid.

web/admin/src/lib/layers/orgConfigParse.ts[12-16]
web/admin/src/lib/layers/orgConfigParse.ts[159-169]
web/admin/src/lib/layers/orgConfigParse.test.ts[68-80]
internal/config/config.go[20-63]
web/admin/src/lib/layers/configRepo.ts[44-53]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The shared role allowlist is applied to `agents[].role`, but Go no longer supports role-based agent entries and rejects mappings with `role` but no `source`. Update the frontend parser, validator, tests, and downstream agent handling to use Go's current source/name/enabled agent schema rather than accepting the legacy format.

## Issue Context
`ValidRoles()` applies to defaults and repository role lists, not organization agent registrations. The new parameterized test currently asserts that legacy agent entries using every role are valid.

## Fix Focus Areas
- web/admin/src/lib/layers/orgConfigParse.ts[3-16]
- web/admin/src/lib/layers/orgConfigParse.ts[108-121]
- web/admin/src/lib/layers/orgConfigParse.ts[154-169]
- web/admin/src/lib/layers/orgConfigParse.test.ts[68-82]
- web/admin/src/lib/orgs/orgListRow.ts[88-89]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 56 rules

Grey Divider

Tip of the day
💡 Did you know, you can copy the agent prompt from any finding and feed it to your IDE agent

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread web/admin/src/lib/layers/orgConfigParse.ts
…urce/name/enabled

VALID_ROLES was missing fix, retro, prioritize, and e2e — add them
for defaults.roles validation. Separately, the agent type still used
the legacy role/name/slug format that Go rejects since ADR 0045
Phase 4. Update the TypeScript agent type, parser, and downstream
consumers to use Go's current source/name/enabled schema with
DerivedName() logic for name extraction.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Shai Revivo <srevivo@redhat.com>
@ascerra

ascerra commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

/fs-review

@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown

Site preview

Preview: https://0a4f44c0-site.fullsend-ai.workers.dev

Commit: 1edfda5723e7e6d1e4d9fec783ba80fc7fcd9416

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 19, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 10:08 AM UTC · Completed 10:26 AM UTC

Commit: 2477d8d · View workflow run →

@codecov

codecov Bot commented Aug 19, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@fullsend-ai-review

Copy link
Copy Markdown

Review

Findings

Medium

  • [intent-mismatch] web/admin/src/lib/layers/orgConfigParse.ts — The PR title "fix(fix(web-admin): VALID_ROLES has 4 roles vs Go's 8 #2528): sync VALID_ROLES with Go's ValidRoles()" understates the actual scope. Beyond expanding VALID_ROLES from 4 to 8 entries, this PR also refactors the agent config schema from { role, name, slug } to (string | { source, name, enabled }), adds derivedAgentName/sourceBaseName/isAgentEnabled helpers, changes the agentsFromConfig return type from { role } to { name }, and propagates this rename across analyzeOrg.ts, secrets.ts, and orgListRow.ts. The schema alignment with Go's AgentEntry is directionally correct and well-implemented, but the title should reflect the full scope.
    Remediation: Update the title to reflect the full scope, e.g., fix(#2528): sync VALID_ROLES and agent config schema with Go.

Low

  • [scope-creep] web/admin/src/lib/layers/orgConfigParse.ts:12 — The agent type refactoring goes beyond what issue fix(web-admin): VALID_ROLES has 4 roles vs Go's 8 #2528 explicitly authorizes. However, Go's AgentEntry struct already uses Source/Name/Enabled fields and DerivedName() logic, so aligning the TypeScript type is a natural companion to the VALID_ROLES sync — the TS code parses the same YAML that Go parses with a stale schema.

  • [missing-authorization] web/admin/src/lib/layers/orgConfigParse.ts:153 — The per-agent VALID_ROLES check in validateOrgConfig was removed. This aligns with Go behavior: Go's Validate() uses ValidateAgentEntries() for structural checks (name format, source URL allowlists, duplicate detection) — not role membership. ValidRoles() only applies to defaults.roles in both Go and TS.

  • [input-validation] web/admin/src/lib/layers/orgConfigParse.ts:150 — Agent names now flow from user-supplied source/name values into secretNameForRole/variableNameForRole without an allowlist check. Risk is minimal: Go pre-validates agent names with validConfigAgentName regex (^[a-zA-Z0-9][a-zA-Z0-9_-]*$), the TS admin is a read-only dashboard, and Octokit URL-encodes path parameters.

  • [naming-convention] web/admin/src/lib/layers/secrets.ts:5secretNameForRole and variableNameForRole retain "Role" in their names and role as their parameter despite now being called with agent.name. Cosmetic inconsistency with 2 call sites.

  • [input-validation] web/admin/src/lib/layers/orgConfigParse.ts:166sourceBaseName accepts arbitrary strings without character validation. Go handles this via validConfigAgentName regex and case-insensitive duplicate detection in ValidateAgentEntries(). The TS implementation mirrors Go's DerivedName() by design.

  • [edge-case] web/admin/src/lib/layers/orgConfigParse.ts:170derivedAgentName returns an empty string when an agent entry has no source and no name (e.g., { enabled: true }), producing malformed secret names like FULLSEND__APP_PRIVATE_KEY. Go's ValidateAgentEntries rejects this case (enabled entries must have a source), so this path is unreachable for valid configs.

  • [naming-convention] web/admin/src/lib/layers/orgConfigParse.ts:16VALID_ROLES constant name still references "role" despite the agent schema moving to name-based naming. However, the constant validates defaults.roles entries, which are still called "roles" in both Go and TS schemas — the name is accurate for its current purpose.

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.


Note: The following inline comments could not be posted on the diff (GitHub returned 422) and are included here instead:

  • web/admin/src/lib/layers/orgConfigParse.ts:14: [medium] intent-mismatch

The PR title "fix(#2528): sync VALID_ROLES with Go's ValidRoles()" understates the actual scope. Beyond expanding VALID_ROLES from 4 to 8 entries, this PR also refactors the agent config schema from { role, name, slug } to (string | { source, name, enabled }), adds derivedAgentName/sourceBaseName/isAgentEnabled helpers, and propagates the role-to-name rename across multiple files.

Suggested fix: Update the title to reflect the full scope, e.g., fix(#2528): sync VALID_ROLES and agent config schema with Go.

  • web/admin/src/lib/layers/orgConfigParse.ts:12: [low] scope-creep

The agent type refactoring goes beyond what issue #2528 explicitly authorizes. However, Go's AgentEntry struct already uses Source/Name/Enabled fields and DerivedName() logic, so aligning the TypeScript type is a natural companion to the VALID_ROLES sync.

  • web/admin/src/lib/layers/orgConfigParse.ts:153: [low] missing-authorization

The per-agent VALID_ROLES check in validateOrgConfig was removed. This aligns with Go behavior: Go's Validate() uses ValidateAgentEntries() for structural checks (name format, source URL allowlists, duplicate detection) — not role membership.

  • web/admin/src/lib/layers/orgConfigParse.ts:150: [low] input-validation

Agent names now flow from user-supplied source/name values into secretNameForRole/variableNameForRole without an allowlist check. Risk is minimal: Go pre-validates agent names with validConfigAgentName regex, the TS admin is read-only, and Octokit URL-encodes path parameters.

  • web/admin/src/lib/layers/secrets.ts (file-level): Line 5 · [low] naming-convention

secretNameForRole and variableNameForRole retain 'Role' in their names and 'role' as their parameter despite now being called with agent.name. Cosmetic inconsistency with 2 call sites.

Suggested fix: Rename to secretNameForAgent/variableNameForAgent and parameter from role to name.

  • web/admin/src/lib/layers/orgConfigParse.ts:166: [low] input-validation

sourceBaseName accepts arbitrary strings without character validation. Go handles this via validConfigAgentName regex and case-insensitive duplicate detection. The TS implementation mirrors Go's DerivedName() by design.

  • web/admin/src/lib/layers/orgConfigParse.ts:170: [low] edge-case

derivedAgentName returns empty string when an agent entry has no source and no name, producing malformed secret names. Go's ValidateAgentEntries rejects this case, so this path is unreachable for valid configs.

  • web/admin/src/lib/layers/orgConfigParse.ts:16: [low] naming-convention

VALID_ROLES constant name still references 'role' despite the agent schema moving to name-based naming. However, the constant validates defaults.roles entries, which are still called 'roles' in both Go and TS schemas — the name is accurate for its current purpose.

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Aug 19, 2026

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review findings

Four findings on the agent-schema refactor that ships alongside the VALID_ROLES sync — 2 HIGH, 2 MEDIUM, posted inline. Summary:

  • HIGH web/admin/src/lib/layers/secrets.ts:33 — secret/variable names are now derived from agent names; Go derives them from roles, so the layer looks up credentials that cannot exist.
  • HIGH web/admin/src/lib/layers/orgConfigParse.test.ts:101 — the new test asserts a fixture is valid that Go rejects for two independent reasons, and validateOrgConfig still claims Go parity while porting none of ValidateAgentEntries.
  • MEDIUM web/admin/src/lib/layers/orgConfigParse.ts:114 — the removed typeof guard lets a non-string agents[].name surface as a raw TypeError on the org row instead of degrading.
  • MEDIUM web/admin/src/lib/layers/orgConfigParse.ts:167sourceBaseName diverges from Go's DerivedName() on trailing slashes and dot-leading basenames.

Review-only; no changes requested and no approval implied.

Comment thread web/admin/src/lib/layers/secrets.ts Outdated

for (const agent of agents) {
const sName = secretNameForRole(agent.role);
const sName = secretNameForRole(agent.name);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

HIGH — Secrets layer now keys secret/variable names off derived agent names; Go keys them off roles

secrets.ts is documented as a read-only port of SecretsLayer.Analyze (internal/layers/secrets.go), but this PR changes the value fed into secretNameForRole/variableNameForRole from agent.role to agent.name, where name is now the derived agent name (explicit name:, or the basename of source:).

Verified on this head, Go always names these credentials from the role:

  • internal/layers/secrets.go:123/134 call secretName(agent.Role) / variableName(agent.Role); secretName is FULLSEND_%s_APP_PRIVATE_KEY of strings.ToUpper(role) (secrets.go:183-188).
  • Every construction of layers.AgentCredentials iterates a role list — internal/cli/admin.go:1434 (for _, role := range roles), internal/cli/admin.go:2025 (for _, role := range config.DefaultAgentRoles() in runAnalyze, the exact path this TS mirrors), internal/cli/github.go:658, and toAgentCredentials(role, ...) at admin.go:2284.
  • AgentEntry.DerivedName() is used only for the harness registry, dispatch matching, poll, and fullsend agent listing (harness/registry.go:50, cli/run.go:3799, cli/poll.go:238, config/interfaces.go:240) — never for a secret or variable name (grep APP_PRIVATE_KEY under internal/ returns only role-based constructions).

Concrete regression: for the config in this PR's own new test (source: https://example.com/coder.yaml#sha256=… + name: my-coder), the UI now queries FULLSEND_MY-CODER_APP_PRIVATE_KEY (confirmed by running the shipped helpers). GitHub Actions secret names cannot contain hyphens, so that secret can never exist and the layer permanently reports not_installed/degraded for any org registering a custom agent — while the real FULLSEND_CODER_* credentials go unchecked. analyzeOrg.ts:13 was updated to assert the wrong contract in prose too ("Agent names from org config (drives secret/variable names)").

Note: the separate false-green when a config has no agents: key at all (config.NewOrgConfig never emits one, so agentsFromConfig returns [] and the layer reports "installed" having checked nothing) is pre-existing, but this PR re-commits to that source of truth in a rewritten function and doc comment.

Suggestion: Feed the secrets layer from roles, matching Go's analyze path — pass roles: string[] (from cfg.defaults.roles, falling back to Go's config.DefaultAgentRoles() set when absent, which is what runAnalyze uses) instead of agents: {name}[], and restore the analyzeOrg.ts:13 comment to say roles drive secret/variable names, citing internal/layers/secrets.go secretName/variableName. Keep agentsFromConfig/derived names only for callers that genuinely need agent identity. Add a test asserting a config with defaults.roles and no agents: key still checks FULLSEND_<ROLE>_APP_PRIVATE_KEY for every role.

agents:
- role: not-a-valid-role
- source: harness/triage.yaml
- source: https://example.com/coder.yaml#sha256=abc123

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

HIGH — New test asserts a config Go rejects twice is valid; all agent validation dropped while the doc comment still claims Go parity

The new test "accepts agents with source-based entries" asserts validateOrgConfig(cfg) === null for a fixture containing - source: https://example.com/coder.yaml#sha256=abc123. Verified against ValidateAgentEntries on this head (internal/config/config.go:436-443):

  • urlutil.ParseIntegrityHash requires a 64-hex sha256, so abc123 yields hasHash=falseagents[1] (my-coder): URL source must include a valid #sha256=<64-hex-char> integrity fragment.
  • Even with a valid digest, the fixture has no allowed_remote_resources, so example.com fails MatchingAllowedPrefixInListURL %q is not covered by allowed_remote_resources.

So the PR whose stated purpose is UI/Go parity ships a test hardcoding a UI/Go divergence, and the fixture will mislead the next person who treats it as a valid-config example.

More broadly, validateOrgConfig (line 132) still carries the doc comment "matches Go Validate errors" while performing zero agent validation. Go's Validate calls ValidateAgentEntries, which enforces: source required on enabled entries (config.go:410), explicit name on disabled entries (config.go:407), derived name against ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ (config.go:415), case-insensitive duplicate names (config.go:420-426), https-only sources, no http://, no other :// schemes, no absolute paths, no .. traversal (config.go:444-460).

Sub-point: a legacy - role: triage entry now passes the TS shape checks and derives name "" (confirmed by running the helpers), where Go's AgentEntry.UnmarshalYAML fails at parse time with "agents entry uses legacy role/name/slug format (removed by ADR 0045 Phase 4)" — i.e. the stale-format config this migration targets is exactly the one the UI stops reporting.

Suggestion: Fix the fixture to a real 64-hex #sha256= digest under an allowed_remote_resources-covered prefix (or flip the assertion to the expected Go error string), and port the checked subset of ValidateAgentEntries into validateOrgConfig: require source on enabled entries, an explicit name on disabled entries, ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ on derived names, https-only with a valid integrity fragment plus allowlist match, reject http://, other schemes, absolute paths, backslashes and .., and detect case-insensitive duplicates. Add negative tests mirroring each Go error string, including the legacy - role: entry. If a narrower scope is intended, at minimum narrow the validateOrgConfig doc comment to state which Go validations are and are not ported.

}
for (let i = 0; i < doc.agents.length; i++) {
const el = doc.agents[i];
if (typeof el === "string") continue;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

MEDIUM — Removed typeof guard lets a non-string agents[].name escape as a raw TypeError on the org row

This PR deletes the only type assertion on agent fields (agents[${i}].role must be a string) and replaces it with if (typeof el === "string") continue;, validating nothing inside mappings (orgConfigParse.ts:112-118).

Reproduced by running the shipped helpers in Node: a YAML name: 42 parses to a number under the core schema, derivedAgentName returns it unchanged (if (entry.name) return entry.name; — line 163 does not check the type), agentsFromConfig returns [{name: 42}] without throwing, and the throw only happens later in secretNameForRole (secrets.ts:6): TypeError: r.toUpperCase is not a function. That throw originates inside analyzeSecretsLayer, which runs at orgListRow.ts:104outside the inner try/catch whose comment deliberately degrades gracefully ("invalid YAML — still analyze other layers with empty agents/repos", orgListRow.ts:100) — so it lands in the generic outer catch at orgListRow.ts:133-137 and the whole org row renders a raw JS internal message instead of degrading.

For accuracy: the neighbouring case of a non-string source does not have this problem — source: 123 throws inside sourceBaseName during agentsFromConfig at orgListRow.ts:89, which is inside the inner try, so it degrades correctly. Only the name path escapes.

Related: a quoted enabled: "false" is !== false, so isAgentEnabled keeps the agent enabled with no type check.

Suggestion: In assertOrgConfigShape, assert name and source are strings when present and enabled is a boolean when present, using the same agents[${i}].<field> must be a … message style as the check that was removed.

return sourceBaseName(entry.source ?? "");
}

function sourceBaseName(src: string): string {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

MEDIUM — sourceBaseName does not mirror Go's DerivedName() as its doc comment claims

agentsFromConfig is documented as mirroring config.OrgConfig.Agents, and sourceBaseName hand-reimplements Go's AgentEntry.DerivedName() (internal/config/config.go:74-85, path.Base + strings.TrimSuffix(base, path.Ext(base))). Running both implementations side by side on the same inputs, they disagree:

input Go DerivedName() this TS
harness/ harness ""
harness/triage.yaml/ triage ""
x/.yaml "" (then rejected by Go's name regex) .yamlFULLSEND_.YAML_APP_PRIVATE_KEY
.yaml "" .yaml

The dotIdx > 0 guard is what breaks dot-leading basenames, and split("/").pop() is what breaks trailing slashes where path.Base strips them. (For an empty source Go returns "", not "."path.Ext(".") == ".".)

This matters because the derived name is now the secret-lookup key (see the HIGH finding on secrets.ts), and this PR drops the derived-name regex check Go applies at config.go:415, so a divergent name reaches the GitHub API instead of failing validation. The existing [input-validation] note on this function states the TS implementation "mirrors Go's DerivedName() by design" — the runs above disprove that specific claim.

Suggestion: Strip trailing / before taking the last segment and mirror path.Ext for dot-leading basenames (no dotIdx > 0 special case), then add Go's ^[a-zA-Z0-9][a-zA-Z0-9_-]*$ check on the derived name so remaining malformed cases fail validation instead of producing a lookup key. Add unit tests for harness/, harness/triage.yaml/, .yaml and "" pinning the Go-matching values.

shairevivo and others added 2 commits August 20, 2026 10:09
…s, not agent names

Go's secrets layer keys credentials by role (from defaults.roles),
not by derived agent name. Replace agentsFromConfig (which derived
names from agent source) with rolesFromConfig (which reads
defaults.roles directly). This matches Go's AgentCredentials.Role
flow. Remove the now-unnecessary derivedAgentName/sourceBaseName
helpers and their edge-case-prone reimplementation of DerivedName().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Shai Revivo <srevivo@redhat.com>
waynesun09
waynesun09 previously approved these changes Aug 20, 2026

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Validated at head 1edfda57:

  • VALID_ROLES now exactly matches Go's ValidRoles() (internal/config/config.go:184) — all 8 roles, and the error string mirrors Go's invalid role %q: must be one of … format.
  • The agents schema rewrite matches Go's AgentEntry (string | {source, name, enabled}), and dropping the per-agent role check is correct post-ADR-0045: Go validates roles only on defaults.roles/per-repo roles, never on agents.
  • My previous findings are all fixed at head: secrets analysis is keyed off defaults.roles via rolesFromConfig (matching Go's AgentCredentials{Role: role} + secretName(role) in internal/layers/secrets.go), the name-derivation helpers (derivedAgentName/sourceBaseName) and their divergences are removed entirely, and the test fixture now uses local-path sources Go actually accepts.
  • Tests cover all 8 roles parameterized, the invalid-role rejection, and both rolesFromConfig paths. CI green.

Two residual gaps, both non-blocking for a read-only dashboard, fine as follow-ups:

  1. TS accepts legacy role:-only agent mappings that Go rejects with the ADR-0045 error, and doesn't port ValidateAgentEntries (sha256 integrity fragment, remote-resource allowlist, duplicate names) — so the admin UI can report a config as valid that the CLI rejects. Worth a code comment stating that agent-entry validation is intentionally out of scope here.
  2. Go's Validate() also rejects duplicate roles in defaults.roles (config.go:341); the TS validator doesn't, so roles: [triage, triage] shows valid in the UI but fails in Go.

@waynesun09
waynesun09 dismissed their stale review August 20, 2026 14:49

Dismissing my approval — see the comment below. This is not a code-quality issue; web/admin is being removed.

@waynesun09

waynesun09 commented Aug 20, 2026

Copy link
Copy Markdown
Member

Closing this, and I want to be clear that the reason has nothing to do with the quality of the work.

Work on the web admin SPA is stopped, and we have decided to remove web/admin/ from the repo for now. This PR is correct — I validated it at head 1edfda57 and my findings were all addressed — but it targets code that is on its way out, so merging it does not make sense.

Follow-ups on our side:

  • Remove web/admin/ and its docs, and relabel the open web-admin issues (including fix(web-admin): VALID_ROLES has 4 roles vs Go's 8 #2528) so neither contributors nor the code agent pick them up again.
  • Add a "paused areas" note to CONTRIBUTING.md so this is visible from the contributor path rather than from one deployment doc.

@waynesun09 waynesun09 closed this Aug 20, 2026
@fullsend-ai-retro

fullsend-ai-retro Bot commented Aug 20, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ❌ Failure · Started 2:51 PM UTC · Completed 2:51 PM UTC

Commit: 1edfda5 · View workflow run →

waynesun09 added a commit that referenced this pull request Aug 20, 2026
Work on the web admin SPA is stopped and the feature is being removed
rather than left paused. Paused-but-present code kept advertising itself
as live work: the pause was recorded only in the title of
docs/web-admin-deployment.md, while CI still ran the admin test suite and
issue #2528 carried both good-first-issue and ready-to-code, so the code
agent and human contributors were both routed at it (PR #6369).

Removed:

- web/admin/ (Svelte SPA) and web/docs/ (orphan test whose source was
  already gone)
- The OAuth BFF in the site Worker: /api/oauth/authorize, /api/oauth/token
  and /api/github/user, plus oauthCors.ts and the admin API tests. The
  Worker is now a passthrough to the ASSETS binding.
- The two [[ratelimits]] blocks, run_worker_first, and the wrangler.toml
  patch script that existed only to keep their namespace ids unique. The
  deploy already passes --name explicitly, so the name patch was redundant.
- GITHUB_APP_* and TURNSTILE_* wiring from site-deploy.yml (both the
  production deploy and the PR preview upload), sample.env.local, and the
  matching miniflare test bindings.
- The root Vite build, svelte-check, eslint config, the Svelte half of the
  Prettier config, and every runtime npm dependency, all of which were
  admin-only. vite.config.ts survives as a vitest-only config so the
  VitePress theme tests keep running.

Note that removing run_worker_first makes the passthrough the Worker's
only code path. The ASSETS binding was never declared in wrangler.toml,
which was harmless while the Worker only ran for /api/* (those paths
returned JSON before reaching the ASSETS branch) but would now 503 every
request that reaches the Worker. wrangler.toml declares
binding = "ASSETS", and a test asserts it is present.

Unaffected, and verified so:

- The VitePress documentation site builds and ships unchanged; docs:build
  passes and /docs/ is still assembled into the deploy bundle.
- The public mint at mint.fullsend.sh is a separate Worker provisioned
  from internal/dispatch/cf/ with its own wrangler.toml. No file under
  internal/ is touched.
- e2e/admin/ is the CLI install e2e suite, not the SPA, and is untouched.

Cloudflare-side secrets (GITHUB_APP_CLIENT_SECRET, TURNSTILE_SECRET_KEY)
and the FULLSEND_GITHUB_APP_* / FULLSEND_TURNSTILE_* repo secrets and
variables are now unused and should be deleted out of band.

The admin SPA was never rolled out, so this is not a user-facing breaking
change and carries no `!` marker: there is no deployed UI for anyone to
migrate off. Installation has always been driven by the CLI
(`fullsend github setup`, `fullsend repos`). Stale /admin/* paths fall back
to the landing page rather than 404, since not_found_handling stays
"single-page-application".

Refs #2528

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
waynesun09 added a commit that referenced this pull request Aug 20, 2026
Work on the web admin SPA is stopped and the feature is being removed
rather than left paused. Paused-but-present code kept advertising itself
as live work: the pause was recorded only in the title of
docs/web-admin-deployment.md, while CI still ran the admin test suite and
issue #2528 carried both good-first-issue and ready-to-code, so the code
agent and human contributors were both routed at it (PR #6369).

Removed:

- web/admin/ (Svelte SPA) and web/docs/ (orphan test whose source was
  already gone)
- The OAuth BFF in the site Worker: /api/oauth/authorize, /api/oauth/token
  and /api/github/user, plus oauthCors.ts and the admin API tests. The
  Worker is now a passthrough to the ASSETS binding.
- The two [[ratelimits]] blocks, run_worker_first, and the wrangler.toml
  patch script that existed only to keep their namespace ids unique. The
  deploy already passes --name explicitly, so the name patch was redundant.
- GITHUB_APP_* and TURNSTILE_* wiring from site-deploy.yml (both the
  production deploy and the PR preview upload), sample.env.local, and the
  matching miniflare test bindings.
- The root Vite build, svelte-check, eslint config, the Svelte half of the
  Prettier config, and every runtime npm dependency, all of which were
  admin-only. vite.config.ts survives as a vitest-only config so the
  VitePress theme tests keep running.

Note that removing run_worker_first makes the passthrough the Worker's
only code path. The ASSETS binding was never declared in wrangler.toml,
which was harmless while the Worker only ran for /api/* (those paths
returned JSON before reaching the ASSETS branch) but would now 503 every
request that reaches the Worker. wrangler.toml declares
binding = "ASSETS", and a test asserts it is present.

Unaffected, and verified so:

- The VitePress documentation site builds and ships unchanged; docs:build
  passes and /docs/ is still assembled into the deploy bundle.
- The public mint at mint.fullsend.sh is a separate Worker provisioned
  from internal/dispatch/cf/ with its own wrangler.toml. No file under
  internal/ is touched.
- e2e/admin/ is the CLI install e2e suite, not the SPA, and is untouched.

Cloudflare-side secrets (GITHUB_APP_CLIENT_SECRET, TURNSTILE_SECRET_KEY)
and the FULLSEND_GITHUB_APP_* / FULLSEND_TURNSTILE_* repo secrets and
variables are now unused and should be deleted out of band.

The admin SPA was never rolled out, so this is not a user-facing breaking
change and carries no `!` marker: there is no deployed UI for anyone to
migrate off. Installation has always been driven by the CLI
(`fullsend github setup`, `fullsend repos`). Stale /admin/* paths fall back
to the landing page rather than 404, since not_found_handling stays
"single-page-application".

Refs #2528

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
waynesun09 added a commit that referenced this pull request Aug 20, 2026
Work on the web admin SPA is stopped and the feature is being removed
rather than left paused. Paused-but-present code kept advertising itself
as live work: the pause was recorded only in the title of
docs/web-admin-deployment.md, while CI still ran the admin test suite and
issue #2528 carried both good-first-issue and ready-to-code, so the code
agent and human contributors were both routed at it (PR #6369).

Removed:

- web/admin/ (Svelte SPA) and web/docs/ (orphan test whose source was
  already gone)
- The OAuth BFF in the site Worker: /api/oauth/authorize, /api/oauth/token
  and /api/github/user, plus oauthCors.ts and the admin API tests. The
  Worker is now a passthrough to the ASSETS binding.
- The two [[ratelimits]] blocks, run_worker_first, and the wrangler.toml
  patch script that existed only to keep their namespace ids unique. The
  deploy already passes --name explicitly, so the name patch was redundant.
- GITHUB_APP_* and TURNSTILE_* wiring from site-deploy.yml (both the
  production deploy and the PR preview upload), sample.env.local, and the
  matching miniflare test bindings.
- The root Vite build, svelte-check, eslint config, the Svelte half of the
  Prettier config, and every runtime npm dependency, all of which were
  admin-only. vite.config.ts survives as a vitest-only config so the
  VitePress theme tests keep running.

Note that removing run_worker_first makes the passthrough the Worker's
only code path. The ASSETS binding was never declared in wrangler.toml,
which was harmless while the Worker only ran for /api/* (those paths
returned JSON before reaching the ASSETS branch) but would now 503 every
request that reaches the Worker. wrangler.toml declares
binding = "ASSETS", and a test asserts it is present.

Unaffected, and verified so:

- The VitePress documentation site builds and ships unchanged; docs:build
  passes and /docs/ is still assembled into the deploy bundle.
- The public mint at mint.fullsend.sh is a separate Worker provisioned
  from internal/dispatch/cf/ with its own wrangler.toml. No file under
  internal/ is touched.
- e2e/admin/ is the CLI install e2e suite, not the SPA, and is untouched.

Cloudflare-side secrets (GITHUB_APP_CLIENT_SECRET, TURNSTILE_SECRET_KEY)
and the FULLSEND_GITHUB_APP_* / FULLSEND_TURNSTILE_* repo secrets and
variables are now unused and should be deleted out of band.

The admin SPA was never rolled out, so this is not a user-facing breaking
change and carries no `!` marker: there is no deployed UI for anyone to
migrate off. Installation has always been driven by the CLI
(`fullsend github setup`, `fullsend repos`). Stale /admin/* paths fall back
to the landing page rather than 404, since not_found_handling stays
"single-page-application".

Refs #2528

Assisted-by: Claude
Signed-off-by: Wayne Sun <gsun@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

requires-manual-review Review requires human judgment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(web-admin): VALID_ROLES has 4 roles vs Go's 8

3 participants