Skip to content

feat(accounts,frontend): installed access follows pages, a failed install retries in place, refusals and module actors read in words - #1164

Closed
antoinetoussaint-byte wants to merge 2 commits into
mainfrom
fix/installed-access-follows-pages
Closed

antoinetoussaint-byte wants to merge 2 commits into
mainfrom
fix/installed-access-follows-pages

Conversation

@antoinetoussaint-byte

@antoinetoussaint-byte antoinetoussaint-byte commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

What we are trying to do

An organization administrator opening Admin → Modules & solutions should see, for each installation, whether people need access to it, and should be able to recover a failed installation without uninstalling it.

Before → after

Installed table, a module with no pages (e.g. a records or tracing module)

  • Before: Teams column "No team" and an outlined "Grant a team" button on every module row — reads as if every module needs a team.
  • After: Access column shows one muted line — "No pages of its own. Grant access only if a solution calls it on a person's own session; modules and solutions that mint their own access need none." — with a secondary (ghost) "Grant access". No "No team".

Installed table, an installation with pages (a solution today, a module with pages tomorrow)

  • Before: "No team" + "Grant a team".
  • After: "No one has access yet" + "Grant access" (outlined). Grants still list as holder · level (team · role), removable; inherited grants unchanged.

Failed installation

  • Before: Status "Failed" + reason, Health "Unavailable", Teams "Available after activation", Action "Retry" (outline) beside "Uninstall".
  • After: Status "Failed" + reason + "Retry installation runs the install step again; nothing needs to be uninstalled first."; Health "Not running"; Access "Available once the installation succeeds"; Action "Retry installation" (primary, RetryInstallation) before "Uninstall". Pending rows read "Not running yet" / "Available after activation".

Refusals: a host refusal on these RPCs now reads in words ("role "r" is not a role in this org") instead of cannot grant scope: rpc error: code = FailedPrecondition desc = ….

Recent activity / audit table: a module acting on its own principal reads "Module " instead of "Actor unavailable".

Why access is not hidden on headless rows

Access is keyed on pages, not on kind (a module may get pages later). But a solution can also call a module on the person's own session (no minted capability), and the host then admits that person only through a grant on the module's installation. The delivered manifest does not record that session consumption today (it carries only audience-bound bindings), so the host cannot tell which headless modules a person reaches. Hiding the action would break such a call; the page therefore keeps it, secondary, with the muted explanation. Closing that gap: #1163 (needs a handbook proposal: it moves the manifest contract).

The grant affordance is named "Grant access" and the dialog asks for a holder and an access level (team + role today) so per-member grants and read/write levels add a field, not a new shape: #1162 (also proposal-first).

Changes

  • accounts: AvailableSolution.has_pages (field 10), from the registration's frontend half in the catalogue statement (r.frontend_revision IS NOT NULL), carried on installed rows via OrganizationInstallation.target. AuditEvent.actor_module (field 16), set in QueryAuditLog from the module principal registry at read time. mapInstallationError / mapInstallationExposureError answer with the carried status's own message via the existing innerStatus helper.
  • frontend: solution-installations model (PageReach, HEADLESS_ACCESS_NOTE), page and grant dialog wording; resolveActor(actorId, directory, actorModule) used by the activity feed and the audit table.
  • Generated (regenerated, not edited): Go/TS bindings, OpenAPI, module/contracts, module.package.codefly.yaml, SDK vendored contract + bindings. AuditEvent is on @codefly-dev/saas-sdk's published surface; main's unreleased 0.3.32 (since v0.0.101) already covers it, and kit-version check passes.
  • Docs: module/SOLUTION_REGISTRATION.md, module/services/frontend/AGENTS.md, module/services/accounts/AGENTS.md.

Reuse

Reused: the existing RetryInstallation RPC and mutation (only relabelled and promoted), the registration's frontend half (the same fact navigation uses), the module principal registry (Service.ModulePrincipals), the adapters' innerStatus helper, GrantInstallationToTeam/RevokeInstallationFromTeam unchanged. Deliberately not done: no new grant RPC, no inference from kind or names, no synthetic principal-directory rows for modules (the directory stays stored identities only).

Classification

Fix at the owner for each behaviour: the pages fact and module actor name come from accounts; the wrapped-status text is fixed in accounts' mappers rather than stripped in the browser. The headless-row explanation is an interim presentation until #1163 lets the host compute person reach; labelled as such in the code comment and docs.

Module playground / story

No new shared kit component, so no kit story. The admin page itself is where a person exercises this; the container tests drive it with the host's wire shapes.

Independent review

One fresh-context review round on the rebased head (b1150ee): merge with follow-ups, no blocker. Dispositions:

  • Major — the has_pages doc comment said a grant is offered only where it is true, contradicting the page: fixed (comment reports pages only; regenerated).
  • Minor — the solution dashboard's recent events still showed a module actor as unavailable: fixed (actorModule threaded through newestEvents).
  • Minor — the installation mappers now return the inner status message for host-failure codes (Internal/Unavailable) too, where callerFacingRefusal (R1019-N10) would log and replace it: follow-up, not changed here. This PR strictly reduces exposure (before, the whole chain text was returned, inner message included), and several install-path Unavailable statuses carry reasons an administrator acts on (registry stale, envelope revision), so replacing them with a fixed phrase needs its own decision about which are refusals and which are failures.

Verification

  • go test ./pkg/adapters (accounts) — includes the updated error-mapping tests and TestAuditActorModuleNamesOnlyADeclaredModulePrincipal.
  • go test ./pkg/infra (accounts, real Postgres via Codefly) — includes TestAvailableSolutionTargetReportsDeclaredPages.
  • go test ./... in module/tools; codefly generate contracts saas-starter --check, codefly generate runnables saas-starter --check; a no-change proto regeneration was clean before the edit.
  • Frontend: vitest — solution-installations (61), audit, activity-feed pass; full run 4809 pass, 4 fail in onboarding/*.pipeline.test.ts, which need a running Codefly graph ("Codefly did not provide the frontend gateway context") — not run here. tsc --noEmit, eslint on the touched trees, node scripts/ci/kit-version.mjs check, naming gate, scripts/check-boundaries.sh.

Not verified: a live codefly run of the page, and the business DB suite (go test ./pkg/business) beyond what CI runs. Rebased on main after #1160 (store pin) merged; all generated artifacts regenerated on the merged tree, not merged by hand.

🤖 Generated with Claude Code

…tall retries in place, refusals and module actors read in words

The Modules & solutions page showed "No team" and "Grant a team" on every
installed module, which reads as if every module needs people granted to it,
and a failed installation offered only a bare "Retry" beside "Unavailable"
and "Available after activation".

- accounts: AvailableSolution.has_pages says whether the applied generation
  declares pages, read from the registration's frontend half (the record the
  navigation is built from), never from the kind. It rides on the installed
  rows through OrganizationInstallation.target.
- accounts: the installation RPC error mappers answer with the carried
  status's own message (innerStatus), so a refusal no longer reads as
  "cannot grant scope: rpc error: code = ... desc = ...".
- accounts: QueryAuditLog names a module acting on its own principal
  (AuditEvent.actor_module, the prefix the module principal registry declares
  it under) at read time; nothing is stored on the row.
- frontend: the Teams column becomes Access; "Grant a team" becomes
  "Grant access" and the dialog asks for a holder and an access level (team
  and role today), so a member holder or a level ladder adds a field rather
  than a new shape (#1162). A row with pages and no grant says "No one has
  access yet"; a row without pages drops "No team" and shows one muted line
  saying when a grant still matters, with the action kept but secondary,
  because a solution may reach that module on a person's own session and the
  host cannot see that consumption yet (#1163).
- frontend: a failed installation shows "Retry installation" as the primary
  action with a line saying nothing needs uninstalling; Health reads
  "Not running" and Access "Available once the installation succeeds".
- frontend: Recent activity and the audit table name a module actor
  "Module <name>".
- Regenerated: Go and TS bindings, OpenAPI, contracts, the package manifest
  and the SDK's vendored contract. AuditEvent is on @codefly-dev/saas-sdk's
  published surface; the unreleased 0.3.32 already on main carries it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@antoinetoussaint-byte
antoinetoussaint-byte force-pushed the fix/installed-access-follows-pages branch from a5d42d2 to b1150ee Compare October 18, 2026 19:26
…ion dashboard names module actors too (#1164 review)

- The has_pages doc comment claimed a catalogue offers a grant only where it
  is true; the page keeps access on headless rows because a solution may call
  them on a person's own session. The comment now says it reports pages only.
  Regenerated bindings, OpenAPI, contracts and the SDK's vendored contract.
- The solution dashboard's recent events carry actorModule through
  newestEvents, so a module acting on its own reads "Module <name>" there as
  it does in Recent activity and the audit table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@antoinetoussaint-byte

Copy link
Copy Markdown
Contributor Author

Superseded in part by #1248 (a7b16db, refusals in words) and in part by the authority's installation-owned role scopes (codefly-dev/authority#7) — re-cut has_pages / retry UX on the new model if still wanted.

auto-merge was automatically disabled October 26, 2026 18:52

Pull request was closed

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.

1 participant