Repository navigation
authority-core: what a second host needs (memberships, journal, delivery reading, method admission, store contract) and two cases every host runs - #35
Merged
Conversation
…eading, method admission and a store contract, shared by every host A person may belong to several organizations: Tx.Member(org, id) is what the core reads what they hold from, and a caller acts only in their session's organization. The reading of a verified delivery (FromCore, ceilings, roles, installation-owned roles, methods, owner-aware, the document's pairing) moves from the local composition plugin into package delivered; method admission moves from the local state into decide.AdmitMethod and Service.CheckMethod; the owner refusal and the method-served kinds move into ApplyModule; module installations at an organization added later are Service.OrganizationAdded. Package storetest is the contract every Store meets, run here on memstore. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and the host grants a role as the administrator would ReconcileOrganizations holds every composed module at every organization (what a host runs at start; the local authority's fixture load now calls it), and GrantRoleAsHost is trusted host setup deciding a grant as GrantRole decides it for the organization's administrator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ransaction that makes it Config.Journal receives each change (a module applied or withdrawn, an installation requested, activated or uninstalled, a role granted or revoked, a delegation granted or revoked, a resource attached, detached or transferred) inside its write transaction; a journal that refuses aborts the change. A host maps it onto its audit record; nil journals nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ounded purpose, and an administrator may end it A delegation names a module installed at the grantor's organization (nothing could issue from one to a module installed nowhere there) and a purpose of at most 1024 characters. Its grantor or an administrator of its organization, acting there, ends it; anyone else learns nothing of it, not even that it exists (NotFound, no longer a Denied that confirmed it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and only the hop registers an admission key Two cases every host now runs: an administrator acts on no organization but the session's (Review, Install, List, ListRoleGrants, GrantRole, RevokeRole), an absent installation answers as a foreign one, a module nobody composed is NotFound by name, and another organization's delegation is never read or ended; and only the hop registers an admission key, published once among the admission keys with the hop-admission purpose and never among the Work Context keys, renewed by registering it again, refused by name to another workload and for a key that is not 32 bytes. Fixture gains OtherAdminToken and HopCredential; the local authority passes both (49 cases). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…it (domain.ErrAlreadyInstalled) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…odule installation is missing Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the whole kind decide.OrgScopes dropped RoleScope.Resources, so a person holding collection:read on store-x alone held the whole kind at Issue: an empty ask was issued kind-wide, an ask naming store-y was issued on store-y, and every child inherited it. Only ListResources answered correctly, so any module deciding from the token's claims over-granted. OrgScopes now carries a role's ID matchers as the held scope's resource ids (a scope naming none, or All, stays the whole kind and absorbs), so Issue refuses any other id or the whole kind and an empty ask carries exactly the held ids. A token cannot carry a prefix or a pattern, so one holds only the ids the decision names (the ask, or the parent capability of a hop or a delegation) that it matches: an empty ask carries none of it, fail closed. held/throughSolutions pass what each decision names. The shared suite's "which resources a context covers" case now issues on the covered ids, requires an empty ask never to carry the kind whole, and requires an ask naming an uncovered id (beside a covered one) or the whole kind to be refused. Local tests that encoded the kind-wide token are updated to the new contract; decide and authority tests cover exact ids, asks beyond, children, patterns, whole-kind roles and mixed roles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eaders hold TestCurrentInstallationIsConfinedToTheAuthenticatedAudience (from main, #34) issued rob and omar whole-kind read though their role reads store-x alone: the escalation the previous commit closes. The test is about installation confinement, so each asks for what they hold. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…only one that does Config.HostInstallsModules says the host decides which organizations hold each composed module, by its own administrators' consent, and its store answers those installations from the host's records. The core then installs no module at delivery, for a new organization or when reconciling; withdrawal leaves the host's records to the host; UninstallAsHost refuses a module. A narrowing redelivery still raises the installation's revision, which the store applies to the host's record. Unset, every composed module is installed at every organization, as before. Workload.Organization binds a workload to the organization its credential is bound to: an issue naming another is refused, and an issue naming none acts there. A workload issue also refuses an installation that is not active. installationOf prefers an organization's active installation of a target, so a host that keeps the records of retired installations beside the one it made again seals capabilities to the live one; a capability sealed to the retired one stays refused after the reinstall. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… which every host's store writes A host that installs modules by consent keeps a module's installations in its own records (Config.HostInstallsModules), so the contract proves atomicity on the installation the core writes for every host. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he ones the suite delivers Fixture.InstallModule (and SolutionPrincipalCase.InstallModule) has a host whose administrators install each module by their own consent install a module the suite just composed, through the host's own path. nil keeps delivery installing a module at every organization, as the local authority does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The local authority takes main's authority-client pin. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s interface 0.3.4 requires After main's per-instance hop admission key (#32), RegisterAdmissionKey requires a fresh proof signed by the key it registers over the credential and the authority's audience. The case signs each registration (stamp.ProveRegistration) with the audience the hop's credential names, and the library takes main's authority-client. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
antoinetoussaint-byte
added a commit
to codefly-dev/module-saas-starter
that referenced
this pull request
Oct 27, 2026
…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>
crisfiorini-oss
pushed a commit
to crisfiorini-oss/module-saas-starter
that referenced
this pull request
Oct 27, 2026
…ty core (codefly-dev#1257) * wip: accounts serves the host interfaces through the shared authority core Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * wip: regenerate Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * wip: legacy delegations into the core, gates, store contract Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * wip: product fixture installs kept beside the core's; suite isolation and admission cases on accounts Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(accounts): HOST_INTERFACE.md describes the host interfaces as served now; pin authority 8093104c Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(accounts): the solution-principal target runs against accounts; 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> * feat(accounts): a narrowing of authority applies directly, recorded in 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> * fix(accounts): a module is installed at an organization only by its administrator'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 codefly-dev#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> * fix(contracts): the exported proto trees carry the recorded interface 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> * chore(source-read-sdk): regenerate for the accounts contract digest Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(go): accounts and auth-gateway pin frontendpayload at a commit on main They required module/frontendpayload at 0d423cb, a commit on no branch, so a fresh module cache could not fetch it. They now require it at 6fd56da, main's latest change to that module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: run CI against the corrected pull request body 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> * fix(accounts): the authority core issues within what each module installation 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 codefly-dev#36's head (93bc9f58), which carries codefly-dev#35's merge. - The NarrowInstallationScope comment no longer names a policy receipt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): an envelope write ends every core capability decided under 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 codefly-dev#36's head (22b86ccb). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- 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 SaaS accounts as thin wrappers around it. The local authority already runs on
libraries/authority-core/go. This PR adds what accounts needs to run on the same core (codefly-dev/module-saas-starter, the companion PR, which pins this branch), and moves the host-agnostic logic that still lived in the local authority's plugins into the core, so neither host keeps a copy.What changed
Core extensions accounts needs, each host-agnostic and unit tested:
Tx.Member(org, id). A person may belong to several organizations. Accounts has multi-organization users, and memstore assumed one organization per person. The core now reads what a person holds throughMember.Person(id)only answers whether the principal is a person. A caller acts only in the organization their session was opened at (memberAs). memstore keeps one membership per (person, organization) and gainsDeleteMember. Test:TestAPersonOfTwoOrganizationsActsOnlyAsTheMemberOfTheSessions.Config.Journal. Each change of authority is journaled inside the write transaction that makes it, so a host's audit record commits with the change or not at all. The changes are: module applied or withdrawn; installation requested, activated or uninstalled; role granted or revoked; delegation granted or revoked; resource attached, detached or transferred. A journal that refuses aborts the change. Test:TestEveryChangeIsJournaledInItsOwnTransaction.OrganizationAdded,ReconcileOrganizations. An organization created after the modules were delivered holds them, as earlier organizations do. The local fixture load now uses this.Config.HostInstallsModules(security review blocker 2). A host whose administrators install each module by their own consent sets it, and its store answers those installations from the host's records. The core then installs no module at delivery, for a new organization or when reconciling; withdrawal leaves the host's records to the host;UninstallAsHostrefuses a module. A narrowing redelivery still raises the installation's revision, which the store applies to the host's record. Unset (the local authority), every composed module is installed at every organization, as before. Tests:TestAHostThatInstallsModulesIsTheOnlyOneThatDoes,TestAWorkloadIsIssuedOnlyWhereItsHostInstalledIt(table-driven, including a capability ending with its installation and staying dead after a reinstall).Workload.Organization. A workload bound to one organization (its credential's) is issued nothing at another, and an issue naming none acts there. A workload issue also refuses an installation that is not active.installationOfprefers the active installation of a target, so a host keeping retired installation records beside a new one seals to the live one.decide.OrgScopescarries a role's id matchers as the held scope's resource ids, so an ask beyond them, or for the whole kind, is refused and an empty ask carries exactly the held ids.GrantRoleAsHost. Trusted host setup decides a grant the wayGrantRoledecides it for the organization's administrator.Composed. Reads a composed module as it was delivered.domain.ErrAlreadyInstalled. The install refusal can be tested by sentinel.ReconcileOrganizationsmakes one read and writes only for an organization that is missing a module installation.storetestasserts that an empty id names no member. This caught a bug in accounts' Postgres store, fixed in the companion PR.NotFound, never aDeniedthat would confirm the delegation exists. Accounts enforced these rules on its own before; they now live once in the core. The local test moves fromErrRefusedtoErrUnknown.Moved out of the local authority into the core, so accounts reuses them instead of keeping a second copy:
delivered. Reads a verified delivery as the authority's module:FromCore, scope ceilings, roles, installation-owned roles, method policies, owner-aware, the pairing of the authority document with its carrier (VerifyDocument,Pair), and the withdrawal refusal. A host decodes its own generatedAuthorityManifestV1intodelivered.Document. The core links no generated interface package, because a second registration of the same proto file panics. Tests:delivered/delivered_test.go.decide.AdmitMethodandService.CheckMethod. Method admission and HTTP route matching. Advisory mode returns the same refusal that an enforced check reports. Table test:TestMethodAdmission, 26 rows.ApplyModulechanges. It now refuses a delivery that names a resource owner anywhere, and joins the kinds a module's methods require to what it serves (decide.Served). These checks were in local'sapplyManifestLocked.Package
storetest. The contract everyauthority.Storemust meet: transactions are all or nothing, a missing record wrapsErrNotFound, every record reads back as written, grants are idempotent, every revision and epoch only grows, and identity reads return what the host seeded. It runs on memstore here and on accounts' Postgres store in the companion PR. It uses fresh ids, so it can run against a database other tests share.Conformance: two new cases every host runs. The local authority passes all 49 cases, and accounts passes the same 49.
every installation call stays inside the caller's organization, and an absent installation answers as a foreign one:PermissionDeniednaming the organization fromReview,Install,List,ListRoleGrants,GrantRoleandRevokeRoleat another organization;NotFoundby name inDelegateandReview;only the hop registers an admission key, published apart from the Work Context keys:stamp.KeyIDand the purpose ishop-admission;admission_keysand never amongkeys;InvalidArgument.FixturegainsOtherAdminToken,HopCredentialandInstallModule(a host that installs modules by consent installs the ones the suite delivers;SolutionPrincipalCase.InstallModulelikewise). The admission-key case signs each registration (stamp.ProveRegistration, over the audience the hop's credential names), as interface 0.3.4 requires. The conformance module's authority-client pin moves to main (7b6569e1).656d23bf, fix: hop registration proves possession; REST callers keep {"error"}; setup page hardened (#28, #30 follow-ups) #32): the local authority takes main's authority-client pin; generated contracts merged without conflict and the package gate passes.Use cases → tests, on both hosts
The shared suite (
conformance.Run) runs against the local authority (plugins/registry_test) and against accounts (pkg/adapters/host_interface_conformance_test.go). Each of the following passes on both.child_intersection_test,child_provenance_test,scope/model_testdelegation_rules_test,journal_testinstallation_owned_scopesdecide/installation_owned_test,principal_testconsent_testrevocation_test,organization_added_test(GrantRoleAsHost)attachment_testr143_test,decide_testdecide/method_testissuer/admission_test, accountshost_interface_admission_testand the Postgres lease testsmembership_testr152_teststoretest(memstore here, Postgres in accounts)Fix or hack
Every item is a fix placed where the behaviour belongs.
delivery.VerifyandDigest,hop/go/routing.RouteMatches, andstamp.KeyID.Evidence
At head
e0b04b28:go vet ./...andgo test -count=1 ./...(GOWORK=off) pass in all 12 Go modules, includingtools/packagegate(contracts matchcodefly generate contracts) andtools/noproxygate.Currentcase included) and the solution-principal target passes.InstallModule.Not verified
🤖 Generated with Claude Code