Repository navigation
feat(accounts): the host interfaces are decided by the shared authority core - #1257
Merged
Merged
Conversation
… core Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and admission cases on accounts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rved now; pin authority 8093104c Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the suite at authority main runs in CI; an empty id names no member The shared solution-principal target (18 cases) now runs in the harness beside the suite. Membership loss is applied to accounts' membership rows directly, a labelled stand-in for accounts' own policy-logged membership narrowing. A new CI job runs the suite at authority main whenever main contains the pinned revision. Member(org, "") read as every member; it now names nobody (codefly-dev/authority storetest pins it). Role grants, revokes and listings leave the tenant decision to the core, which answers another organization's installation and an absent one alike. Pins authority 8846431f. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n the audit journal Owner decision (2026-10-27): removing a team or organization member, and every other change that only reduces access, applies directly, its audit record in the same transaction. No policy-log witness is asked first; widening paths are unchanged. - business/narrowing.go is the one path every narrowing takes; the policy log (append, receipt, commit, serving gate, reconciliation) is deleted, with its Postgres and warehouse adapters, its interceptor gate and its refusals. - The module envelope writer writes a narrowing like a grant: the row, the installations the ceiling no longer allows and the audit event commit in one transaction. The pure-grant classification that only chose the witness is deleted. - A withdrawn solution target closes in its apply transaction. - Migration 57 drops policy_log_commits and policy_log_cursor. - The policy-log service, which only witnessed narrowings and was never wired, is deleted with its declarations, configuration and generated topology. - Member removal audit events name the person removed (user_id). - The solution-principal target removes team and organization members through RemoveTeamMember and RemoveOrgMember; the harness proves the removal is in the audit journal and that the leaver's earlier session is issued nothing. - "Host interface conformance (authority main)" is a required context and a release gate; its script reads authority's ancestry from a blobless clone, retried, never the rate-limited API. - Proto doc comments, contracts, the SDK bindings and the docs say narrowings apply directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dministrator's consent Security review blocker 2: every composed module was active at every organization in the authority core, so a module workload could be issued its headless scopes, and a member a person capability at its audience, in an organization that never installed it, outside its envelope tenant pin, or after an administrator uninstalled it. - The core is configured with HostInstallsModules (codefly-dev/authority#35): it installs no module at delivery, organization creation or start. A module's installation is accounts' own installations row of kind module (an administrator's install with consent, within the module envelope), which the Postgres store answers as the core's installation, live. A native uninstall or a narrowed ceiling ends it in the core at once; a capability sealed to it fails its next check and stays dead after a reinstall. The core's one write, a revision raise on a narrowing redelivery, is kept in authority_core_module_revisions (migration 56). - A workload is bound to the organization the module envelope pins it to (Workload.Organization), read from the fenced registry. - Re-pinned to #35 at e0b04b28, which carries the resource-scope fix (blocker 1) and authority main (656d23bf): the fixture's suite viewer gains an organization-wide role; InstallationService.Current is served through the core; RegisterAdmissionKey verifies the 0.3.4 registration proof, uses each nonce once and pins one key per hop instance (migration 58); module.codefly.yaml declares authority 0.3.4 and installation 0.3.0. - The harness installs every delivered module through accounts' own install path (Fixture.InstallModule) and proves a workload is refused where the module is not installed, outside its pin and after an uninstall. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… digests The interface protos' DIGESTS.sha256 moved after the contracts were last generated; codefly generate contracts copies it into both exported trees. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A re-run replays the event payload it started with, so the naming gate kept reading the body before its correction; a new commit carries the current one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…allation consented to Review finding N1: the core read a module installation's status and revision only, so a new workload issue used the delivered binding, ignoring an administrator's narrowing (Capabilities off, a binding dropped), the module envelope's per-binding ceiling, and a widening redelivery not yet re-consented. Before this PR the native mint honoured all three. - codefly-dev/authority#36 adds Installation.Bindings, one ceiling every issue through a binding applies (workload, installation, hop, delegation). - accounts' store fills it for each module installation from the consent recorded on it, decided as the native mint decides it (business/authority_core_consent.go: the registry grant, manifest within envelope, intersected with the consent by consentedGrant). An installation whose consent allows no capability, or that awaits re-consent, is not active for the core. The registry is fenced against the envelope revision committed in the core's own transaction. - The harness proves, beside the native mint: an installation narrowed to no capability issues nothing, a binding dropped from the consent or from the module's grant within the envelope issues nothing, and a redelivery that widens a binding issues no more than was consented. A module's consent is given again through the fixture's install path at each delivery. - Pins authority at #36's head (93bc9f58), which carries #35's merge. - The NarrowInstallationScope comment no longer names a policy receipt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nder the ceiling it replaced Review finding N3: an envelope narrowing a module's binding changed what the core issued next, but a capability issued before it kept passing Check until its TTL, because the revision the core seals to and checks never moved. The module installation revision accounts reports to the core now adds the envelope revision committed in the core's own transaction, beside accounts' own revision and the core's raise, so every envelope write ends what was issued under the ceiling it replaced; codefly-dev/authority#36's workload fence (N2) refuses a mint that straddles the write. The harness writes a real envelope revision narrowing a binding: the earlier capability fails its next check, the next issue fails closed until the registry is derived again, then issues only what the narrowed ceiling allows. Without the revision change the case fails. Pins authority at #36's head (22b86ccb). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 27, 2026
feat(accounts): narrow authority through the policy-log witness, verified against a pinned key
#1067
Closed
Closed
crisfiorini-oss
pushed a commit
to crisfiorini-oss/module-saas-starter
that referenced
this pull request
Oct 27, 2026
…ites nothing (codefly-dev#1259) Every reconcile pass converges the fixture's installations, and an installation that cannot converge yet (a solution whose install step is not acknowledged, so its teams wait) is retried each pass. The retry resolves the installation's solution role through EnsureDerivedRole, which upserted the role and deleted and re-inserted every permission. roles and role_permissions carry BEFORE triggers that move the organization's authorization revision on every row they see, an upsert that changes nothing included, so each pass moved the revision about nine times per role and every capability minted before it was refused ("authorization revision superseded: minted at revision 2473, issuer is at 2495"). EnsureDerivedRole now reads the role and its permissions and writes only what differs: a missing role, a changed description, a permission removed or added. Idle reconcile passes and idle fixture convergence leave the revision where it is; a real change still moves it. The fold of the envelope revision into module installation revisions (codefly-dev#1257) is not the cause: reconcile passes alone left the revision unchanged. Co-authored-by: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <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.
What we are doing
The owner's settled design: one host-independent authority core with unit tests, and the local authority and accounts as thin wrappers around it. Before this PR, accounts implemented the host interfaces a second time, about 3,000 hand-written lines in
host_interface_*.go. It was three interface revisions behind authority main and failed 13 of the shared suite's cases at main.accounts now serves
codefly.dev/authority@0.3.4,composition@0.2.0,installation@0.3.0andsession@0.2.0throughcodefly-dev/authority'slibraries/authority-core/go. That is the same core the local authority runs, here over accounts' Postgres. The core extensions accounts needed are in codefly-dev/authority#35, which merged at50e76eb2; this PR pins codefly-dev/authority#36 (22b86ccb), which builds on that merge.What changed
pkg/infra/postgres_authority_core.goand migration 56.authority_core_*control-plane relations: modules and tombstones, installations, role grants, delegations, epochs, owner-aware revisions, and consumed nonces.ErrConflictwhen retries run out.host_interface.gowas rewritten. It is now the wire and this host's own concerns:signedInPerson), or a workload credential through the execution reviewer (workload);Keyspublishes;requireOrgMember,requireOrgAdmin).Issue(all five owner shapes),Checkwith method policy and advisory,ListResources,Delegate,RevokeDelegation,GetDelegation,AttachResource,DetachResource,AuthorizeTransfer,Review,Install,GrantRole,RevokeRole,ListRoleGrants,List.RegisterAdmissionKeykeeps feat(accounts): the hop registers its admission key; Keys publishes it (authority 0.3.3) #1256's leased store and workload review, and now importsstamp.KeyID. The restated derivation (the labelled stopgap) is gone.ApplyManifestkeeps accounts' delivery owner: verification, the inbox, the reconciler and its audit. The core then composes the applied delivery through its shareddeliveredpackage. A delivery the core would refuse is refused before anything is recorded, for example a solution declaring no installer role, which the local authority already refused. When the core is configured at start, it composes every delivery accounts has already applied, so existing cells need no redelivery.Config.Journal. The events are the newsaas.authority.installation_requested,installation_activated,installation_uninstalled,role_granted,role_revoked,resource_attached,resource_detachedandresource_transferred, plus the existingsaas.module_delegation.grantedandsaas.module_delegation.revoked.task_startedorchild_session_startedin the organization's transaction before it is handed out. An issue whose record cannot be written is refused.HostInstallsModules(Generate bootstrap from reviewed snapshot (#33) #35): it installs no module itself. A module's installation is accounts' owninstallationsrow of kindmodule(an administrator's install with consent, within the module envelope), which the Postgres store answers as the core's installation, live. A native uninstall, or a ceiling that stops allowing the organization, ends it in the core at once; a capability sealed to it fails its next check and stays dead after a reinstall (new installation id). The core's one write to a module's installation, raising its revision when a redelivery narrows the module, is kept beside the row (authority_core_module_revisions, migration 56), because accounts' revision is lifecycle-owned. A workload is bound to the organization the module envelope pins it to (Workload.Organization, Generate bootstrap from reviewed snapshot (#33) #35): an issue naming another is refused. Organization creation and start no longer install every module.Installation.Bindings, one ceiling every issue through a binding applies. accounts' store fills it from each module installation's recorded consent, decided exactly as the native mint decides it (business/authority_core_consent.go: the registry grant, manifest within envelope, intersected with the consent byconsentedGrant), with the registry fenced against the envelope revision committed in the core's transaction. An installation narrowed to no capability is not active for the core; a binding dropped from the consent or from the module's grant within the envelope issues nothing; a widening redelivery issues no more than was consented.business/narrowing.go). The policy log (append, receipt, commit, serving gate, reconciliation), its adapters, its tables (migration 57) and the never-wiredpolicy-logservice are deleted. Member-removal audit events now name the person removed.RegisterAdmissionKeyverifies the registration proof (stamp.CheckRegistration, over the credential and accounts' audience), uses each nonce once across replicas (hop_admission_proofs) and pins one key per hop instance, the reviewed pod (hop_admission_keys.instance, migration 58).InstallationService.Currentis served through the core'sCurrentInstallation, authenticated by the module's workload credential.Current, authority gains the registrationproof), andmodule.codefly.yamldeclares authority 0.3.4 and installation 0.3.0. Everything was regenerated only throughcodefly generate proto, the cataloggo generatesteps, the audit contribution update,module-compose,codefly generate contractsandcodefly generate runnables, and the SDK scripts inscripts/ci/check-generated-libraries.sh. The source-read SDK needed--forceonce, for the new contract digest. CLI v0.1.215. A no-op regeneration came back clean before any change.OfferService, the local authority's setup read, is not served.HOST_INTERFACE.mdwas a 1,261-line log and is now a 75-line description of what is served and where.What moved into the core, and what was deleted here
host_interface_hop.go,_delegation.go,_holdings.go,_limits.go,_resources.go,_installation_owned.go,_solution_roles.go,_workload.goand_installation.go, with their unit tests. The suite and the core's tests cover the same rules.business/module_delegation.goandinfra/postgres_module_delegations.go.AdmissionKeyID.module_delegationstable: migration 56 carries its rows into the core's table as they stood, and its down migration carries them back.saas.accounts.v1.WorkContextServiceand the module capability tier). The native paths do not serve the host interfaces.Conformance: the shared suite at #35 against accounts
50 of 50 shared cases pass (the new
Currentcase included), all 18 of the solution-principal target pass, and so do 9 harness cases (77 in all). The suite runs withgo testinpkg/adapters, over accounts' Connect listener, with modules and solutions delivered as signed carriers throughApplyManifest. Every module the harness or the suite delivers is installed at both fixture organizations by their administrators through accounts' own install path (Fixture.InstallModule, #35); nothing installs a module by hand any more, andconformance-idlesimply stays installed nowhere.Beyond the suite, the harness proves:
RemoveTeamMemberandRemoveOrgMember(production wiring, no test-only log): the removal applies at once, the leaver's earlier session is refused (not a member of this organization), and each removal is in the audit journal naming the person removed and the administrator.The fixture's suite viewer (Bob) gains an organization-wide
Record viewerrole: since #35's resource-scope fix (review blocker 1), a collection grant holds only the collection, so the viewer the suite issues kind-wide needs a kind-wide role, as the local authority's viewer has.The 13 cases that failed at authority main on #1256 all pass now:
PermissionDenied, no existence oracle).Every case (72)
Shared suite (49):
Solution-principal target (18):
Beyond the suite (5):
Fix or hack
ApplyManifestmust declare an installer role (the core's contract). Role-less legacy deliveries still arrive through the HTTP delivery endpoint and are not composed in the core.GrantRole,RevokeRoleandListRoleGrantsleave the tenant decision to the core (policy tenantNONE). The core requires a session at that organization, and answers another organization's installation and an absent one alike.Member(org, "")returned the organization's first member. It now names nobody, and Generate bootstrap from reviewed snapshot (#33) #35'sstoretestpins this for every host.8af4b787tob23dc135, which the authority libraries require, makesrunnable.PackageFromMethodderive a tool exposure. The source-operation tests now assert it.requireOrgMemberandrequireOrgAdmin, the RBAC relations,bump_organization_authorization_revision,emitTx, feat(accounts): the hop registers its admission key; Keys publishes it (authority 0.3.3) #1256's admission store, and the module-delegation audit events.Evidence
Shared suite at #36 (
22b86ccb) against accounts: 50/50. Solution-principal target: 18/18. Harness extras: 9/9.All run locally on the pushed head (see the PR's latest commit), unless noted.
module/services/accounts/code:go vet ./...andgo vet -tags=pure ./...are clean.go test -race -count=1 -tags=pure ./...passes.go test -race -count=1 ./...passes, Postgres tests included: every package on the previous head2db4bce2, andpkg/business,pkg/infraandpkg/adapters(the harness) again on this head.pkg/infra/postgres_authority_core_test.goruns the core's store contract against real Postgres. It covers the identity read from accounts' RBAC, including team-assigned roles and leaving a team.pkg/business/authority_core_audit_test.gochecks that every journaled change is a registered mutation and is recorded as a person or as the system.pkg/business/authority_core_consent_test.gounit-tests the consent decision (table-driven, including the envelope fence).98f56da8, including--replayon PostgreSQL 16, plus migration pairing and the RLS migration gate.codefly generate contracts saas-starter --checkandcodefly generate runnables saas-starter --check, with CLI v0.1.215; the SDK through its ownscripts/generate.mjs.checkandno-broadeningagainst main);scripts/ci/release-gates.mjs checkand its tests (125 pass).module/tools(go test ./..., including the database-authority inventory), the source-read SDK,qualification/module-authority, the auth-gateway and the root module.Host interface conformance (authority main)is now inREQUIRED_CONTEXTSand a release gate (release-gatesneeds it); its script reads authority's ancestry from a blobless clone, retried, never the rate-limited GitHub API.Not done, and why
5a3c7f3b, fix(accounts): bound a module's grant by the installation scope it must fit in, not by 32 bindings #1258's binding bound) without conflict.Uninstallis reachable only from host setup. A module's is uninstalled through accounts' product surface and the core sees it at once.REQUIRED_CONTEXTSalready names it.22b86ccb), which builds on Generate bootstrap from reviewed snapshot (#33) #35's merge50e76eb2. Once Release module starter v0.0.26 #36 merges, this PR re-pins to its merge commit. Every other pseudo-version pin is reachable from its repository's main, exceptcore 835460f16fb5, whichmodule/frontendpayload/go.modalready pins on saas main (it exists on a core branch). accounts and auth-gateway pinfrontendpayloadat main's6fd56dae.audience "<solution>" is not installed in this organizationcannot come from a declared fixture installation. The product's own installation row stays pending until the solution's install step is completed by its consumer, soGrantInstallationToTeamstill refuses until then; that consumer is outside this PR, and the composed platform run that found it was not repeated against this head.🤖 Generated with Claude Code