Repository navigation
fix(frontend): a module page with roles installs through the review, like a solution - #1284
Merged
antoinetoussaint-byte merged 5 commits intoOct 30, 2026
Merged
Conversation
…like a solution Admin -> Modules & solutions sent every module target to the agent-role form, so a module page that declares installer/editor/viewer roles and installation_role none asked for an organisation role and was refused 403 "the delivered module declares no standing machine authority". No wire path could install it: InstallSolution's role_id was a required uuid, so leaving the role out was refused 400 instead. The Install form now follows the authority's answer, not the target's kind (useRolesDeclared, the signal the Setup tab already uses). A module page whose offer names roles opens a review read from that offer (its machine access and what each role permits), is installed through InstallSolution with no agent role, and lands on its Setup page, where each role takes a team. A module the authority refuses (no roles) keeps the agent-role form. The shared authority core installs only solutions (Install refuses a module; its ReviewOffer for a module page carries no consent tuple), so a module page stays this host's installation rather than going through InstallReviewedSolution. accounts: InstallSolutionRequest.role_id may be empty; the host's prepareInstallation already selects a declared installation_role or none, and now refuses an empty role for a target that declares neither. Regenerated with codefly generate proto (CLI 0.1.221) and go generate. The solution and module page reviews share one DeclaredAccess rendering. A long role label no longer widens the agent-role form past the dialog: the form and trigger may shrink and the label truncates, with its full text as the trigger's title. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
Author
Review: module-saas-starter#1284 at 0fcb5bf (base a75fae3)Blocker
Verified sound
Follow-ups
VERDICT: hold — the exported contracts (and then the SDK copy) must be regenerated before CI can pass. |
… the optional role_id Output of `codefly generate contracts saas-starter` and `scripts/ci/check-generated-libraries.sh` (CLI 0.1.221, the CI pin) after InstallSolutionRequest.role_id became optional. The source-read SDK's client step needed `--force` once, because its recorded contract digest moved. No hand edits. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… pages grant teams in Setup Review of #1284. - useRolesDeclaredReview: only the offer review's NotFound means a module declares no roles. Any other failure leaves the answer unknown, and the install page shows it with Try again instead of opening the agent-role form for what may be a module page. useRolesDeclared keeps its contract. - Setup offers a module page no staged team-per-role picker. The picker binds to a solution's consent digest, and a module page's offer has none, so it could only stay disabled. Its roles take teams through Setup's own role grant form. The setup test that gave a module offer a digest now uses the shape the authority sends. - The host conformance suite now installs a module page whose installation_role is a named role with an empty role_id, as the review sends it, and asserts that the agent's one standing grant is that declared role. - AGENTS.md states the NotFound rule, the browser-side approval and the Setup path. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The SDK's generated InstallSolutionRequest binding changed with the contract regeneration, so the published package moves to a new version, as the kit-version gate requires. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The refusal (local QA, 2026-10-30, after #1282)
Admin → Modules & solutions → Install on a module page that declares the person roles
installer,editorandviewerandinstallation_role: noneopened the agent-installation form. That form asks for an organisation role. With one chosen, the host refused:With "Module viewer" chosen, the long role label also pushed the Install button off-screen: x=2031 in a 1628-wide viewport.
Cause
Two causes, one on each side of the wire:
solution-installations-page.tsxchose the form bykind. Everykind === "module"target went toInstallModuleDialog, and that form requires an agent role.InstallSolutionRequest.role_idwas a required uuid, so leaving the role out was refused 400 by validation. The 403 above is the refusal you get when you supply a role.prepareInstallationalready handles an empty role, selecting the declaredinstallation_roleornone. Until now only the in-process supporting-module convergence could reach it.The handoff proposed sending a module page through
ReviewSolutionInstall→InstallReviewedSolution. That route is refused twice:reviewSolutionInstallrejectsKind != "solution".Installrefuses a module ("a module is installed by composition").The core's
ReviewOffercomment states the contract: a module page's offer carries its roles but no consent tuple, and its installation stays the host's. This change therefore uses the host's module installation and reviews the module page from the same offer the Setup tab reads.Fix
useRolesDeclared, the signal feat(accounts): a module page's roles are accepted and gate its page like a solution's #1282 uses for the Setup tab:ReviewOffer: its machine access, its call ceiling and what each role permits. The admin must approve before Install is enabled. The install goes throughInstallSolutionwith no agent role, then the page navigates to/admin/solutions/<id>. There, the kit's Setup grants each role to a team.role_idonInstallSolutionRequestis now optional:IGNORE_IF_ZERO_VALUE, and when given it must still be a uuid.prepareInstallationrefuses an empty role for a target that declares neither a named role nornone.codefly generate proto(CLI 0.1.221, the CI pin) andgo generate ./pkg/business ./pkg/adapters ./pkg/cataloggen.useRolesDeclaredReviewtakes only the offer review's NotFound as "declares no roles". Any other failure is shown on the page with Try again, so it never opens the agent-role form for what may be a module page.useRolesDeclaredkeeps its contract.GrantRole).DeclaredAccesscomponent.min-w-0) and the label truncates. The full label is the trigger'stitle.Reused:
useRolesDeclared,OfferService.ReviewOffer, the existinguseInstallModulemutation andInstallSolution,prepareInstallation's declared-role selection, and the kit's Setup grant form. Deliberately not added:InstallReviewedSolution, which the core refuses;Tests
solution-installations-page.container.test.tsx, three new tests (all three fail against main's UI):ReviewOfferwas asked for that target.InstallSolutionis sent with noroleId. The page then navigates to the installation's Setup page.title, and Install sits in the same form. happy-dom computes no layout, so this test checks the CSS that bounds it. A separate Chrome check of the same structure measured the form's right edge at 3883 px before the fix and 1078 px after, inside a dialog ending at 1094 px.installation_role, with an emptyrole_id. It asserts that the agent's one standing grant is that declared role.TestModuleNoStandingSelectionRequiresCurrentPairedDeclarationgains "legacy without a role refused" (fails without the guard).TestInstallSolutionRequestRoleIsOptionalButWellFormed(fails on main's descriptor).npm run typecheck, and the full pure vitest suite: 357 files, 5061 tests.ui-inventorytest, and biome and eslint on the changed files.go test -tags pure ./pkg/business/,go test ./pkg/adapters/, andgo vet.module/toolsgo test ./....codefly generate contracts --checkand the generated-libraries check.InstallSolutioncall in them passes a role. I did not re-run this in the browser against a live composition.codefly generate contracts saas-starter).scripts/ci/check-generated-libraries.sh), with CLI 0.1.221. The source-read SDK's client step needed--forceonce, because its recorded contract digest moved.codefly generate contracts --checkand the library check both pass on the committed tree.@codefly-dev/saas-sdkmoves to 0.3.42 because its generated binding changed (kit-version gate).Classification
Fix, made at the two owners:
No stopgap.
🤖 Generated with Claude Code