Repository navigation
Conversation
StartTask takes operation_bindings: binding ids of the audience module. The host resolves each one the way the module's delegated exchange will (ModuleOperationAudience: the binding as installed at the organization, narrowed by that installation), adds its invoke scopes beside authority_scopes, keeps each resource only if the person holds it, and refuses by name a binding the person holds nothing under. So a consumer names the binding and never its resource ids, which come from the installation. Direct human-owned mints only.
…tract npm run generate -- --force with codefly 0.1.185, so library.codefly.yaml records the contract digest the package now publishes.
|
Independent review (fresh context, read at No authority widening. The binding's scopes join Major, fix before merge. Major/minor. Minor. Tests missing. Partial hold of ids and of scopes (the fixture binding has one id, which hid the major), a resource-confined mint refused, another module's binding name, a same-kind non-equal caller scope, a headless-only binding, Interface consequence (from the authority work): the person shape of the interface's Issue would need the same |
…tates the hop rule The copied interface protos gain a solution's roles on the sketch manifest; digests updated; regenerated with codefly generate proto. The suite dependency moves to the head that states the hop rule (a module hops only on a person's token addressed to it and names the binding it exercises) and the permission matrix (skipped here until this fixture states one). The harness names the workload's module. Suite: 5 of 11 lines green, one skipped; the hop line is red until childHop enforces the parent's audience and names the binding, which follows the person-issue half of #1219. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tates the hop rule The copied interface protos gain a solution's roles on the sketch manifest; digests updated; regenerated with codefly generate proto. The suite dependency moves to the head that states the hop rule (a module hops only on a person's token addressed to it and names the binding it exercises) and the permission matrix (skipped here until this fixture states one). The harness names the workload's module. Suite: 5 of 11 lines green, one skipped; the hop line is red until childHop enforces the parent's audience and names the binding, which follows the person-issue half of #1219. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…g 1, with the gap list (codefly-dev#1217) * fix(accounts): register installation REST handlers * style(accounts): apply canonical test import grouping * fix(accounts): fence invocation against the admitted source declaration * fix(accounts): export the optional declaration fence to consumers * feat(accounts): serve the published host interfaces as adapters; the policy sidecar; the gap list accounts declares it implements the four published host interfaces (authority, composition, installation, session at 0.1.0), carries their protos under proto/ beside its own (bindings via codefly generate proto), and serves them as adapters over what it already does: Keys and Check over the Work Context authority; Issue by owner shape over StartTask, StartInstallationTask, StartChildSession and ExchangeAudience; List over ListInstallations; SignIn over fixture Authenticate. Where no path exists the adapter refuses by name: workload-owned Issue (gateway workload endpoint), ApplyManifest (signed delivery), Install and GrantToTeam (target and role resolvers). The interface protos carry no saas.policy option on purpose; the host's policy for them is business.InterfaceMethodPolicies, read by RPCPolicies when a descriptor carries none, with the catalog descriptions beside it. The solution-side InstallableService is not served. Known red, by design of this branch (HOST_INTERFACE.md): the service catalog assumes one proto package and Connect bindings are keyed by short service name, so cmd/service-catalog refuses the four packages and the registrations cannot be regenerated; the drift tests fail until both are generalised. go build and the policy tests pass. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(accounts): the host interfaces under host-owned names The published interfaces now carry the host's names (publisher codefly.dev, packages host.*.v1 under proto/host/), so this repository carries no consumer name in the copied protos or in anything generated from them. Policy sidecar, adapters, bindings and the design note follow the rename. go build and the policy tests pass; the catalog gaps of HOST_INTERFACE.md are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(accounts): the last consumer names in comments and the module declaration Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(accounts): regenerate the authorization matrix under the host-owned names; two comments Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(accounts): gap 7 closed in the host interface note Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(accounts): the catalog carries the implemented interface packages; registrations resolve by full name The service catalog has one api_package, the service's own, and now lists the interface_packages it implements beside it (catalog.proto field 9). The compiler accepts a procedure of either; the validator likewise; the frontend catalog, the gateway route aliases and the mesh gates read the API package only, since an implemented interface is not a frontend plugin's client and has no compatibility alias. cmd/connect-registration resolves bindings by full service name first (two services may share a short name across packages) and imports each implemented package's generated Go and Connect packages under derived aliases. The authorization matrix counts services by full name. The adapters now enforce the floors their sidecar policies declare (org member for a person-owned Issue and for List; org admin for Install and GrantToTeam), which the lockstep test requires. The team grant names its organisation, as the interface now does. The naming gate skips generated/service-catalog.json, which crossed its 512 KiB scan cap at 40 services: every string in it is a scanned proto identifier or a scanned Go description. Same reasoning as its descriptor skip; a tool fix, named for review. Catalog, registrations, authz catalog, routes, matrix, topology and frontend catalog regenerated. go build, the business, adapters and cataloggen tests and the naming gate pass. HOST_INTERFACE.md: gaps 1, 2 and 9 closed; 3 to 6 and 8 stand. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix(accounts): ApplyManifest declares no audit event; the delivery path emits host_binding_applied itself Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(accounts): the person shape is the caller's own session; protos follow the published interface Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * docs(accounts): the harness, step by step, from the integration tests this package already has Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(accounts): the rung 1 harness — the interface conformance suite against accounts; the digest check on the copied protos The harness boots the test store, seeds the dev-admin fixture, wires the sign-in path as the business tests do, configures the Work Context authority against the store, serves the Connect listener on an ephemeral port and runs the published interface suite (github.com/codefly-dev/authority/libraries/conformance/go, descriptor-driven, linking no generated package) over gRPC. Opt-in through HOST_INTERFACE_CONFORMANCE=1 because it boots a store. Green on the first run: Keys; sign-in; an unknown fixture token refused by name. Red, recorded as the gap list in HOST_INTERFACE.md: a module must be installed per organisation before it is an audience; the host has no effective-permissions read for an empty ask; an unknown audience is refused PermissionDenied where the contract says NotFound; workload Issue, ApplyManifest, Install, GrantToTeam as before. Adapters: a fresh task id per person-owned Issue; the session returns the access token as the bearer; List is an administrator's read over ListOrganizationInstallations; an unknown fixture person is NotFound. The copied interface protos carry DIGESTS.sha256 and a test that fails by file name when either side drifts. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(accounts): the person line of the host interface suite is green; composed modules are installed everywhere; a hidden refusal names its reason Composition installs a module at every organisation (contract decision of 2026-10-22): business.ComposedModuleInstallations answers the live module authority and the seal store for a composed module's installation, held by every member, and wraps the Postgres store for everything else. A control-plane membership read, WorkContextOrgMember, sits beside the other seal reads. The harness wires it; production keeps its reads until the registry derives these rows itself. An uncomposed audience is refused NotFound by name before any mint, through ModulePrincipalID against the derived registry. Check is authenticated by the token it checks and is public like Keys. mapWorkContextError takes the context and logs the one case the caller learns nothing, "cannot issue Work Context", which until now had its reason recorded nowhere. Suite: 4 of 10 green (Keys, sign-in, the whole person line, every refusal with a reason). The six red lines are each a host change of their own and are the gap list in HOST_INTERFACE.md, with one new gap: the host authenticates a child hop by the owner's bearer where the contract authenticates the holder of the parent token. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(accounts): the counted tripwires move for a public Check Check is authenticated by the token it checks, so it leaves the impersonation-restricted set (62 to 61) and joins the public routes (56 to 57). Each baseline says why in its comment. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(accounts): a workload is issued behind the interface from its credential alone; an operation context names its binding ModulePrefixOfWorkload names the one composed module a workload credential authenticates, through the execution reviewer and the existing reverse lookup from a reviewed workload to the principal declaring it; no caller types a module name. The interface's Issue, for a workload, is the host's operation context to an installed audience when a binding is named, and the module's own context to this surface otherwise; an undeclared binding is NotFound by name. The interface's Issue is authenticated by the request's shape (a person by the bearer, a workload by its credential, a child hop by the parent token and the actor's credential), so both interceptors let a bearer-less call of that one method reach the adapter, which refuses a bearer-less person by name; a bearer, when present, is verified as on every other method. An operation context now names the binding it exercises: the seal source resolves module-operation/<principal>/<binding> from the installed grant at the revision of its terms, and the operation-task mint seals it. Until now a module operation context carried no binding, which is what left every runtime hop "stale" at the revision check. A composed installation's scope is the declared grant itself, since the manifest is the ceiling. Suite: 5 of 10 green (Keys, sign-in, the person line, every refusal with a reason, the workload line). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * feat(accounts): a child hop behind the interface, by the parent token and the actor's credential childHop verifies the parent as issued by this host, names the acting module from its credential, re-reads the owner's authority as the owner (the parent token is what proves the owner), refuses a widening hop as PermissionDenied and derives the hop with core; the host's own child session is authorized by the owner's bearer, which the module acting does not hold. The hop names no binding: core keeps the owner's seal installation on a hop and requires a named binding to be granted within it, and a module's binding is granted within its own installation, an open decision recorded on the interface repository. The bearer-less pass-through on the interface's Issue applies only to a call carrying no credential at all; a forwarded identity is admitted as before. The operation-task mint names a binding only when the authority has one. The harness composes documents, org-directory (the page module, one workload, one binding to documents) and model. Suite: 6 of 10 green. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * copy the composition interface's signed-carrier messages into the host ApplyManifestRequest carries the delivery as signed: the manifest document, the DSSE envelope over it, and the delivering workload's credential. Regenerated with codefly generate proto over the template's saas and host inputs; DIGESTS.sha256 updated. The adapter behind it is the next change. * feat(accounts): a person's parent names its module's operation bindings StartTask takes operation_bindings: binding ids of the audience module. The host resolves each one the way the module's delegated exchange will (ModuleOperationAudience: the binding as installed at the organization, narrowed by that installation), adds its invoke scopes beside authority_scopes, keeps each resource only if the person holds it, and refuses by name a binding the person holds nothing under. So a consumer names the binding and never its resource ids, which come from the installation. Direct human-owned mints only. * chore(saas-sdk): regenerate the library from the accounts/connect contract npm run generate -- --force with codefly 0.1.185, so library.codefly.yaml records the contract digest the package now publishes. * chore(accounts): the interface protos at fdb7ea6 and the suite that states the hop rule The copied interface protos gain a solution's roles on the sketch manifest; digests updated; regenerated with codefly generate proto. The suite dependency moves to the head that states the hop rule (a module hops only on a person's token addressed to it and names the binding it exercises) and the permission matrix (skipped here until this fixture states one). The harness names the workload's module. Suite: 5 of 11 lines green, one skipped; the hop line is red until childHop enforces the parent's audience and names the binding, which follows the person-issue half of #1219. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(accounts): run the interface suite at 13 lines, the hop rule and delegate chains The suite dependency moves to the head that states the hop rule and delegate chains. Accounts passes 5 lines, skips 3 this harness does not yet state (matrix, chain, installed solution) and fails 5; HOST_INTERFACE.md records the person half of the hop and the re-derivation the hop needs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: bind authority child issuance to its authenticated actor * feat(accounts): expose read-only fixture identity discovery * feat(accounts): serve what the authority's edge and hop call The host interface protos at codefly-dev/authority 06d0d3b, copied unchanged for the four declared interfaces and regenerated, and the adapters the edge and the hop need behind them: - implements moves to accounts/connect, the listener that mounts the four services; accounts/authority admits only the internal module-authority procedures, so every interface method was refused there. A test holds the declaration to the endpoint whose contract surface serves it. - Issue for a person with an empty ask is everything they hold at the audience (gap 11): candidates from the audience's declared content and every action the host names on it, each kept by the check every mint makes, then decided again by StartTask. - SessionService Current (the perimeter-verified session, no bearer back, refused once revoked) and ListFixturePeople (the development validator's ordered allowlist through the read-only identity lookup; none where fixture sign-in is off). - Check applies a named method: no module has a delivered method policy here (gap 4), so it is refused by name, or reported as the advisory refusal beside the verified capability for a hop in warn mode. - ListResources and the delegation methods are classified and answer Unimplemented by name. - The dev-admin fixture carries the authority fixture's people, tokens, organisations and teams; fixture role assignments may name a person by email, so a composing workspace's fixture assigns its module roles. - An implemented interface's REST mapping is not on this host's surface: the public OpenAPI renderer drops it, as the gateway route compiler already did. The conformance suite at 06d0d3b: 9 pass, 4 fail, 6 skip (was 5/7/7), table in HOST_INTERFACE.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(accounts): a composing workspace's fixture grants, and List naming its target Composed run on the host interfaces (edge and hop against accounts): - Installation List answered the presence's row id as an installation's target, so a frontend binding to the installed solution by name found none. It is the presence's route alias now, the name Install takes; a row whose presence is gone keeps the id. - The fixture people's roles at composed modules cannot be named here, and the composing workspace had no way to state them: the fixture directory is found from the service's own checkout. A workspace now states them in a fixture-grants.json document (roles and assignments in the fixture's own shape), which the seed adds to the selected fixture and validates with it. The module ships an empty default, and the shipped-default gate counts a JSON document as one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): admit declared hosts at a meshed cell's egress addresses on every guarded egress path On a cell whose ambient mesh carries egress, the mesh answers DNS for each declared egress host with an address it allocated (Istio's auto-allocated ServiceEntry addresses, 240.240.0.0/16 by default). That is reserved space, so every guarded egress path refused it: InvokeSourceOperation failed on staging, and webhooks, the Slack notifier, the crawler, object storage and Client ID Metadata Document fetches would fail the same way. One rule, auth.AdmitsDestination, now answers for every guarded path: the resolved address is public (auth.IsPublicDestination, unchanged), or the deployment asserts its mesh (internal-transport/mesh-protected=true) AND the client asked for a hostname declared in internal-transport/mesh-egress-hosts (on its port) AND the address lies in internal-transport/mesh-egress-range (240.240.0.0/16 when empty; bounded to 240.0.0.0/4 or 2001:2::/48). An IP literal, an undeclared host, and every loopback, link-local, metadata or private address stay refused. Hosts or a range without the mesh assertion, or a malformed entry, refuse startup. It is decided on the address actually dialled: auth.GuardedDialContext resolves the requested host and dials each answer as a literal, with the Control hook judging that literal against the host:port the client asked for. The callable source client (operation calls and OAuth token exchanges), the crawler, the object-store connector and the metadata fetch use it; the webhook policy (and the Slack notifier sharing its client) keeps its resolve-and-pin dial and asks the same rule. Reused: the shared classifier, the mesh assertion (meshtransport.Protected), the per-package guard seams and egresstest. Not added: a second classifier or a second allowance mechanism; the declared hosts mirror the environment's egress declaration by hand because Codefly does not deliver it to the service yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): the fixture-grants document is YAML, as Codefly reads documents Codefly reads a configuration document from <group>.yaml (or .secret.yaml); a .json file in a configuration directory is not a group, so a composed run refused accounts with "no configuration found for fixture-grants". The shipped default and the gate's document default are YAML, which also keeps the fixture's own shape and allows comments. HOST_INTERFACE.md's "not verified" note named a composition; it now says "a composition that runs saas as the authority host", as the naming gate requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): a workload's issue through a binding is addressed to the binding's audience A hop's ForModule issues as the module's workload through a binding it declares and names no audience: the binding carries it. accounts refused every such issue with "an issue names its audience", so a module's own work through its binding (a vocabulary install at start, say) never got a Work Context. The audience is now the declared binding's; every other shape still names its audience, and an undeclared binding is NotFound by name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): admit the environment's generated egress hosts, inert without the mesh Codefly now generates the workspace group egress/HOSTS from the environment's egress declaration and refuses an authored one, so accounts declares the group and reads its hosts from it instead of a hand-kept copy in internal-transport/mesh-egress-hosts, which is deleted. Every environment declares its egress whether or not its deployment is meshed, so declared hosts without internal-transport/mesh-protected=true no longer refuse startup: they are inert, and a declared host resolving into the mesh range is refused there like any reserved address. A hand-set mesh-egress-range without the mesh assertion still refuses startup. The mesh cases now install the declared host on unmeshed deployments too, so every guarded path proves the inert case end to end. The configuration gates treat egress as CLI-owned, like artifact-cache: no authored default, and its keys are the CLI's to name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): the composed-installation stand-in is a test package; the defaults gate knows document groups Two gates red on this branch, each fixed where it points: - TestEveryModuleCapabilityPathReReadsLiveAuthority flagged ComposedModuleInstallations (LiveModuleAuthority, composedModuleOf) for reading the declared registry directly. It is no capability path: it stands in for the live module authority in the host-interface conformance harness, and only that test wires it. It moves to pkg/business/compositiontest beside executiontest and installationtest, reading the declaration through the service's accessors that exist to stand in for it; production reads its installations from the seal store, unchanged. - TestEveryDeclaredGroupShipsALocalDefault knew only .env and .secret.env, while Codefly reads a configuration document from <group>.yaml. It now counts a YAML document stating at least one key, and fixture-grants.yaml gets its repo-root symlink like every default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): an unwitnessed envelope narrowing is refused by name, not as an internal error A module envelope write that changes what an existing principal is granted is a narrowing, and with no policy log the host refuses it (ErrPolicyLogUnreachable). PutModuleEnvelope answered that as Internal "cannot write the module envelope", so a composed run that added one binding to a principal retried the write for ever with no reason. It is FailedPrecondition now, saying the policy log's witness is what the write needs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): a child issue through the actor's binding is addressed to the binding's audience A hop's ForChild names the parent, the acting workload and the binding, and no audience; accounts refused it with "an issue names its audience" before the child path could answer. The audience is now the actor's declared binding's, as for a workload's own issue, so the child path answers what it holds today: a hop naming its binding is Unimplemented, waiting on where a cross-module binding is held (gap 14), by name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(accounts): a module's hop through its binding inherits its owner's current grants Issue with a parent token, the acting module's workload and the binding it exercises was refused as Unimplemented ("waits on core deciding where a cross-module binding is held"), which blocked every module calling a module on a person's behalf through the host interface (gap 14). Per the owner's decision (child issuance A), the child is the owner's current grants at the binding's audience within the binding's declared scopes, with the parent's actor chain replayed and the acting module as the last hop naming the binding. The binding is the one this host already holds: the module principal registry as installed in the organisation (ModuleOperationAudience). The child is sealed to the installation that binding is granted within, so core's binding-within-installation rule holds by construction. It descends from the parent's session and never outlives the parent. A narrowed parent stays narrowed: for every kind the parent carries, in its authority scopes or any hop's grant, the child is held to the least of them, and carries those narrowings in its own authority scopes for the next hop. A kind the parent never carried is no narrowing, which is how a chain crosses audiences. Refused by name: an undeclared binding (NotFound) and a parent addressed to another module (PermissionDenied), both before the parent is verified so a single-use parent is not spent; an audience other than the binding's; an owner holding none of the binding; an ask beyond the owner's grants; an ask beyond the parent's narrowing; headless; a non-person owner. The revision check accepts the binding spelling this host seals; a module hop replayed ahead of it stands while its module is installed. The conformance harness states a delegate chain and a lookup case: 11 pass, 4 fail, 4 skip at authority 06d0d3b (was 9/4/6). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): an envelope entry that only gains is a grant, by the installation-consent rule The envelope write skips the policy-log witness only for a pure grant, and no deployment has a log transport, so whatever it classifies as a narrowing cannot be written at all. It compared a kept principal's JSON member by member and required every array to be identical, so a module that declared one more content kind (documents' resources gaining documents.collection and documents.ingest), or one more action on a binding's scope, read as a narrowing and could not be granted; a local run had to wipe its database to move its own composition's ceiling. A kept principal is now compared decoded, by the rule this host already holds an installation's consent to its ceiling with (InstallationScopeWithin over FullInstallationScope): allowlists and scope actions may grow, resource ids may widen or drop, artifact approvals and audiences must hold. The tenant must be unchanged, a cross-tenant grant kept, and every binding's delegation mode kept, as before. A removal, a retarget or resource ids appearing on an unbounded scope still needs the witness. The JSON member walk is deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(accounts): a fixture declares its installations, performed by name once delivered A fresh local run came up with nothing installed: each organisation's administrator installed the wiki and every module by hand through the install API. A fixture now declares `installations` (org, target, role, owner), in the fixture file or in the composing workspace's fixture-grants document, since a target's name and a module's role are the composition's words. The seed refuses, by name, an owner the fixture does not make an owner or admin of the organisation. Nothing is written directly. business.Service.InstallTargetByName resolves the target from the organisation's catalogue (ListAvailableSolutions, the one an administrator installs from) and the role from its role catalogue, then installs through InstallSolution with every check it makes; a held installation (any status but revoked) is returned unchanged. The host interface's Install now uses it (gap 5; GrantToTeam stays Unimplemented). Targets arrive by delivery after the seed, so the declared installations run after every delivery pass (SolutionHostBindingReconciler.AfterEachPass), as their owner: an undelivered target waits and says so once, any other refusal is reported with the pass, naming the installation. A host with no delivery surface says it installs nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): one sealed binding spelling and one module hop rule, as the interface states The host interface's conformance suite names the binding a child exercises <organisation>/<module>/<binding>. accounts sealed module-operation/ <principal>/<binding>. That spelling is deleted rather than accepted beside the new one: ModuleOperationBindingID, the seal source, the operation context's shape check and ConfirmModuleHopBinding read only <organisation>/<module>/<binding>, for a workload's operation context and a hop's child alike, and an id naming another organisation confirms nothing. The organisation is a canonical UUID, which tells the id apart from a source delegation's and an installed binding's. The operation-context mint now carries the module's prefix, and refuses a binding whose prefix does not name its principal. A hop through the interface that names no binding is refused by name before the parent is verified ("a module's hop names the binding it exercises"), as the local authority refuses it. The old shrink-from-parent child is deleted: a module hop has one rule. ExchangeDelegatedOperationAudience stays: composed modules still call it through the Go SDK's module-authority client. HOST_INTERFACE.md records it. Conformance unchanged at 11 pass, 4 fail, 4 skip (authority 06d0d3b). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): persist and enforce signed method-policy delivery * fix(accounts): enforce delivered solution HTTP route policies * fix(accounts): an installation's issue verifies the installation it names The interface's installation Issue never read the installation id: it called the headless mint with no organisation, installation or task, behind a comment claiming the id was checked after the mint. An installation id is not a credential. The issue now requires the actor's workload credential, resolves the module it proves, holds the organisation to that solution's pinned tenant, and refuses, by name and before any mint, an installation that is not that solution's own active installation in the organisation. The mint is the existing headless installation mint, past its internal credential check; an empty ask is what the installation's authority admits at the audience. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): a solution's delivered roles decide what a person holds through it Accounts signed and stored a solution's delivered roles and scope ceilings and read neither, so a person reached a solution, and a module through it, only by organisation roles: the wiki's viewer and editor meant nothing here, and a person holding no organisation grant at documents was refused every documents call the wiki made for them. What a person holds through a solution is now its roles, read from the applied signed authority document (business.PersonSolutionRoles): installer at the installation they own, and the installation's standing role through each team it is exposed to that they belong to, bounded by the delivered ceiling on the solution's own kinds and by its bindings on the kinds it consumes. A person's issue at such a solution is that grant on its own kinds; a hop through a solution on the path (rule A) is those roles within the binding, with a solution audience's own grant met in; a parent issued at a solution stands while its roles still grant what it carries. Organisation roles answer only where no solution on the path declares roles, and keep administering the organisation. Install takes the solution's own role names: the installation's standing role is a role this host derives per organisation, solution and role (the existing derived-role seam, outside the catalogue), and List reads the solution's role name back. A fixture installation names the teams it is exposed to, granted once the installation admits it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(contracts): export the complete host interface with its owning CLI * fix(accounts): ListResources answers which resources a capability covers ListResources answered Unimplemented, while modules are told to ask it which resources of a kind a person may act on (the token carries the kind alone). It now reads the capability back for the asking audience without spending it, requires the capability to hold the action on the kind, and answers from its owner's grants now: a person's solution roles where a solution on the path declares roles; otherwise every resource for an organisation-wide grant, by the check every mint makes, or the ids of their grants limited to one resource (a new read of role assignments). A capability naming ids covers only those the grants cover; any other owner is bounded by the capability alone, as the local authority answers. The procedure's catalog description is regenerated through go generate, and the control-plane call-site inventory through its test generator (the applied-delivery read moved into its own function in the previous commit). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): a solution's standing role is granted at its authority root The previous commit made an installation of a role-declaring solution hold a host-derived role, and GrantScope refused it: every derived role was one the module-access derivation confers with a named cause, never by hand. So installing the wiki with one of its own roles failed at the agent's standing grant, as the conformance install case showed. A derived role whose provenance is a solution's role is now granted only at a solution's authority-root node in the organisation it was derived for: the installation's standing grant and the teams it is exposed to. Every other derived role keeps its rule, and a solution role is still never assigned organisation-wide or shared. A database test proves the root grant and the refusal elsewhere, and fails without the change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(accounts): solution roles, ListResources and installation issuance Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): pass person bindings through the host authority adapter * fix(accounts): a team is exposed with the solution's own role An installation made before solution roles were read holds a catalogue role named after one of the solution's (the built-in viewer) as its standing role. Exposing it to a team granted that role, which does not permit using a solution, so every fixture reconcile pass on an existing store failed with "role does not permit using a solution". The solution's own role did not carry it either, so a fresh install would have failed the same way. A solution's role now permits using the solution, which is what holding one of its roles means. ExposeHostInstallation grants a team, for a solution that declares roles, the solution's own role of the standing role's name, through the existing grant path and its root-node rule; the standing grant is not rewritten and no database wipe is needed. A database test starts from an installation holding a catalogue "viewer" and ends with the team exposed and its member holding the solution's viewer grant; it fails without either half of the change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): one exposure rule, and its refusal names why c86c750d put the solution-role rule in ExposeHostInstallation, beside the grant rather than in it. GrantInstallationToTeam naming no role now resolves the installation's role itself (solutionExposureRole), so every exposure naming no role, the fixture installer's and the interface's alike, takes the solution's own role of the standing role's name, and the helper beside it is deleted. When the solution's role cannot be used, the standing role is granted as before and the refusal says why: no signed delivery, an applied generation delivered without its authority document, no solution roles, or no role of the standing role's name. The test goes through the fixture installer against the real signed delivery path: an installation holding the built-in viewer, refused with the reason while the solution's delivery carries no authority document, then exposed to its team once the solution delivers its roles, its member holding the solution's viewer. It fails without the rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): declare host audit and documentation ownership * fix(accounts): derive fresh-store authority from signed manifests * fix(accounts): isolate composition delivery outcomes by binding * fix(ci): track registry publication and format accounts imports * fix(accounts): the authority interface holds each issue and check to its caller Review r12 of #1217. Check held a named method to the owner's authority scopes, so an actor delegated read passed a method requiring write; it now holds it to the call's effective scopes, the last hop's grant. Issue authenticates each shape before it answers for the composition, so a bearer-less caller learns nothing of which modules are composed and a workload's own context (module-capabilities) is reachable. A workload's issue honours its scopes, operation and lifetime within the installed binding's headless grant and refuses anything beyond it, or a mismatched audience, by name. An exchange forwards the organisation and reads an empty ask as the parent's effective scopes; at the parent's own audience the issue is a renewal. A delegation names its gap, a refused credential names its cause. The hop refuses a parent narrowed to nothing of a kind and runs the audience guards every mint runs; no binding may be addressed to module-capabilities. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): verify an applied authority document once, list resources of the asked kind Review r12 of #1217. Every Check naming a method and every solution-role read re-read the inbox and re-verified two signatures; the verified outcome is now kept per binding for the applied generation and digest it was verified at, so applying another generation is what makes the next read verify again. An unparsable applied row on another binding no longer fails every answer. A resource question no longer reports a registered scope node of another kind as an id of the asked kind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(accounts): the host interface after review r12 Gap 3 is closed (workload Issue is served), and the Issue mapping states every shape as it now is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): admit resource-limited method scopes unchanged * fix(accounts): project person holdings at each binding target * fix(accounts): refuse envelope authority absent from manifests * test(accounts): give the conformance harness every fixture the shared suite needs The suite at authority aa487dff fails a case outright when its fixture is missing. The harness now supplies each one, built through this host's own paths: - a conformance fixture (testdata) with the dev-admin people plus an Acme owner who is not an administrator, a member with no role or team, a resource-limited record grant, and the roles a module's users hold; - three solutions delivered as the CLI delivers them (signed carrier plus the authority's signed document through ApplyManifest), installed through the fixture's declared installations, acknowledged by the host's own install-step claim and finish, then exposed to their teams; - the installed solution, its workload credential and audience; signed solution roles; a module-to-solution tool binding; a delegation; child-scope and parent-limit chains; an installation-owned chain; resource cases; ambiguous and absent installing organisations; install acknowledgement and revocation (the host's uninstall path). External sources stand in at their seams only: the cluster, the composition's module authority (one module withdrawn for the absent case) and the policy log. The suite dependency moves to aa487dff, which takes core to e5014589 (a descendant of the pinned 0a8d16c5). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(accounts): the conformance harness supplies every fixture the suite asks for Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): hold delivered solution roles to the manifest ceiling * fix(accounts): the module tier proves the calling module, never a shared token ModuleCapabilitiesService on the `authority` endpoint asked every caller for the cluster-internal token (CODEFLY_INTERNAL_TOKEN) on top of the Work Context each method already verifies. A static token shared by every workload names no module and adds nothing a forged caller lacks, so the endpoint no longer asks it of the capability surface: each method proves its caller itself — from the module's Work Context (issued by the authority from the workload credential), or for MintSourceOperationContext from the pod's workload credential, reviewed and bound to its build — and its refusal names what was not proven. The handlers' own requireInternalCredential calls (ListReadableSourceCollections, CheckWorkContextRecordAccess, the delegated exchanges, the module-parent authentication) are deleted with it; no flag, no fallback. MintSourceOperationContext is served on the endpoint, as a declared workload mint: datasources are the host's and never the authority interface's, so the module tier is the one place a module can ask for a source's Work Context. The private listener is unchanged, and the two read-only oracles, which name no caller, keep the perimeter credential until each is bound to its caller. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(source-read-sdk): regenerate with codefly generate client; delete the local pipeline The library was the output of cmd/generate, a local pipeline that ran `codefly generate client` and then overwrote its bindings with `buf generate`, pointing policy, jobs and Work Context descriptors at saas-sdk-go while keeping its own saas/events/v1 copy. A binary linking it beside saas-sdk-go registered saas/events/v1 twice and panicked at init, and the curated tree had gone stale against the contract. It is now plain `codefly generate client --from saas-starter/accounts/authority --services ModuleCapabilitiesService` output: self-contained, importing no other SDK, with every datasource seam (ClaimJobs, HeartbeatJob, AckJob, NackJob, FetchDatasourceBlob, FetchDatasourceFiles, MintSourceOperationContext, ListReadableSourceCollections). cmd/generate is deleted. The pinned CLI could not generate from this service at all (it compiled buf.yaml's excluded `codefly/` tree and named vendored files `vendor/...`); the fix is codefly-dev/cli 2c6225ad, which generated this tree. The authored transport exposes InternalGateway for the streaming calls the facade skips, and its tests send no internal token. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(frontend): regenerate collision-free TypeScript SDK exports Regenerate with CLI df447bbc6cb83b844f871393c284960056fdbeae, the owner fix in codefly-dev/cli#1162. Every generated module has its own namespace; the published SaaS facade and direct imports retain their API. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(contracts): export MintSourceOperationContext on the accounts authority endpoint `codefly generate contracts saas-starter` output after 98ee9e7a served the source-delegation mint on the module tier; the exported catalog lists the endpoint's served procedures, so it moves with them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(accounts): the host interface protos at authority 0.2.0 The four interface protos re-copied unchanged from codefly-dev/authority at aa487dff (authority and session 0.2.0: Session gains person_display_name = 7 and organization_display_name = 8; composition and installation change comments only), DIGESTS.sha256 re-recorded, and everything they generate regenerated through Codefly with the CI's CLI source pin (8de0f667): the Go and TypeScript bindings, the raw OpenAPI, the exported contracts and the SDK client. module.codefly.yaml advertises authority@0.2.0 and session@0.2.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): the host interface behaviour the shared suite reaches - Session: SignIn and Current name the person (profile name, else email) and the organisation for display; a non-member session names no organisation. - Issue for a person refuses an uninstalled audience as FailedPrecondition by name before solution roles read an installation that is not there. - A delivered solution named by its alias resolves to its own installation, never to a composed module's. - An installation's issue proves its actor before reading the ask, then reads the named installation for whose it is: NotFound when unknown, and another solution's workload, another organisation, or a pending or revoked installation refused by name. Its empty ask considers the actions its solution's roles name. - A person at a solution through one of its bindings carries what the solution's roles grant within the binding (was Unimplemented). - An empty ask carries resource-limited grants on exactly their resources. - GrantToTeam refuses a non-active installation naming pending or revoked. - Refusal texts name the interface's reasons. - The harness's HTTP-route subtest installs its solution before issuing there. Each fix carries a unit test. Shared suite at authority aa487dff: 37 of 45 pass (was 21 with the fixtures, 13 before them); the 8 still red are listed with their owner in HOST_INTERFACE.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * style(accounts): goimports the host interface tests Standard library, third-party and accounts imports in their own groups, as the lint phase's goimports requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): a person's ask meets their holdings; a dropped kind stays dropped Two interface rules the shared suite holds the host to. An ask is met with what the person holds, action by action: an id they do not hold is dropped and the rest issued, a kind-wide ask they hold only on named resources keeps those, and an asked action they hold on none of what it names is refused by name ("asked beyond what the owner holds"). Resources are narrowed, never actions, and nothing comes back empty. Rule A, never recovered: a person's explicit ask records each audience kind it left out as a denial (a scope naming the kind and no action), and a hop through a binding denies each kind the owner holds through it that the child was not granted. The chain's limits are read per kind and per action, so two actions on different resources each keep their own; a kind narrowed to nothing is carried as a denial instead of refusing every later hop. Core already signs and attenuates these rows. Conformance at authority aa487dff: 40 of 47 direct cases (was 39); parent per-kind limits and resources by grant are green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(ci): pin codefly CLI 0.1.213 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(accounts): the staleness probe exposes a real team with no member The shared suite at authority PR #8 (3222e991) grants the installed solution to a team the host names (Fixture.UnexposedTeam) instead of a made-up id. The harness fixture seeds "Unexposed", an Acme team with no member, through the host's own seed, so the exposure moves the organisation's authority and grants nobody anything. The conformance dependency is pinned at that head. Conformance: 42 of 47 direct cases (40 of 45 shared); "a callee refuses what does not verify, by name" is green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(accounts): the host interface suite at 40 of 45, and what remains Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): the Work Context read oracles prove the calling module, never a shared token CheckAuthorizationRevision and AuthorizeEvidenceRead were the last methods on the module `authority` endpoint that asked for CODEFLY_INTERNAL_TOKEN instead of identifying their caller: a static token every workload holds, which names no module. Each now authenticates the calling module with the proof the module capability surface uses since 98ee9e7a — the module's own Work Context, issued by the authority from its workload credential, verified and held to its current grant and installation (service.AuthorizeModuleCapability). The internal token is not accepted in its place on either listener. Refusals are named: no single module Work Context, or the token alone, is Unauthenticated ("the calling module is not identified ..."); a context that does not verify is Unauthenticated; one naming an undeclared or uninstalled module is PermissionDenied; a host with no module authority is Unavailable. With no method left that names no caller, the endpoint interceptor asks no perimeter credential at all and ModuleAuthorityAuthenticatesCaller is deleted. Callers must present their module Work Context: annotations already does; document-store, runtime (main), model-gateway (main, via saas-sdk-python) and robin (tool-owner, chat replay host) send the token alone today. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(auth-gateway): the admission fake records under a lock TestAccountsWorkloadAdmissionAsksTheIssuerWithinItsBound read what the fake accounts handler recorded while that handler, on the gRPC server's goroutine, had written it with no happens-before edge; -race failed the 503 case. The fake now records under a mutex and the test reads a copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): a mirror fetch joins its size watchdog and reads its bound once The watchdog goroutine guarding a git fetch was signalled but never joined, so it could still be measuring the mirror — and reading the package-level maxMirrorBytes — after the fetch returned and while the mirror was discarded. -race caught it in TestMirrorSizeBoundFailsClosed, which lowers the bound after an earlier fetch. The bound is now read once per fetch on the caller's goroutine, and the fetch waits for its watchdog before deciding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): an installation's agent stands in its organisation while its installation does The seal reads answered only for members, so a capability an installation's agent owns could not be sealed or carry a revision. An agent now stands while its installation is active, the agent is live and an owner of record is still an admin: the conditions the headless mint resolves. Adds the read of the one active installation an agent acts for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(accounts): an installation acting for itself owns its capability as its agent No person is acting, so the installation's issue is owned by its agent, kind agent, with no actor hop and no person's teams. Check and ListResources re-verify the installation it stands on, since a narrowed standing grant moves no revision. A hop from an agent-owned parent inherits the installation's standing grants within the binding (rule A), and stays the agent's. A removed owner is a PermissionDenied decision, not Internal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(accounts): the installation the suite refuses elsewhere is unrelated to it conformance-pages' viewer role grants example-records:write, so documents is not unrelated to it; the suite's refusal case now uses conformance-proposals, whose roles name its own kind alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(accounts): an installation owns its capability as its agent; the suite at 41 of 45 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): resolve a module principal by its name; the binding is granted to the name A hop's actor is the module's name (the host interface's decision). The seal, epoch and revision answers and the revision check resolve a principal to the declared module by name or registry id; a module named as a UUID is refused so the two never collide. A module's installed operation binding is answered as granted to the module's name, with its registry id beside it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): a hop names its actor by the module's name The hop through a binding carries the module's name as its actor, as the binding id already names it; the journal stores the registry id. The seal source answers a module's own headless context, still owned by its registry id for the consumer contract, in its owner's spelling, and every other context by the name, so no module exercises another's binding. Tests cover each comparison against an impostor module. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(accounts): an installation-owned hop names its actor by the module's name Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(accounts): a hop's actor is the module's name; the suite at 43 of 45 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(accounts): module delegations, revoked once and sealed to the grantor's revision Migration 48 records a person's delegation to a module: tenant scoped, forced RLS, the grant immutable, revoked at most once and never deleted. The revocation stamp advances the grantor's authorization revision in the same statement. Revoking runs under the policy log like every other narrowing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(accounts): Delegate, GetDelegation, RevokeDelegation and Issue from a delegation Delegate holds each action to what the person holds now at the module or an audience its bindings reach. Issue from a delegation proves the actor is the delegation's module and issues its one hop for the grantor, the grantor's current grants through the binding cut to the delegation, with the delegation's id on the hop. Every later hop is cut to each stored delegation the chain names, re-read live. GetDelegation, like Issue, is authenticated by the request's shape. Catalog regenerated with the repository's go generate steps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(accounts): a delegation is bounded by its grantor and ends with its revocation Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(accounts): delegations behind the host interface, and what still holds the case red Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(accounts): the hop fixture answers no module delegation; the delegation lookup is a reviewed control-plane site A module's issue from a delegation names no organisation, so its lookup by id runs under the control plane, as the source delegation mint's does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(accounts): the suite at 41 of 45 with delegations and the agent-owned installation Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(accounts): the suite at 45 of 45 with delegations Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(store): renumber this branch's migrations above main's 48 main merged 48_retention_index_gaps (#1254), so 48_module_delegations duplicated its version and 46/47 fell at or below the merge reference's frontier: the reference gate refuses them, and golang-migrate never applies a version below a database's current one, so a database already at 48 would have skipped both silently. 46_authority_delivery, 47_installation_exposure_revision and 48_module_delegations become 49, 50 and 51; every prose and code reference to them follows. The SQL is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): every hop of a chain is held to the binding it went through A capability seals only its last hop's binding, so a child kept verifying while an upstream binding narrowed or was withdrawn. ConfirmModuleChain re-reads each module hop against its module's binding to the next hop (or to the audience), on Check, ListResources, a hop's parent and the consumer revision check. A module actor named by its name reaches storage as its registry id (record access, readable sources, the journal). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): a pinned principal is never issued outside its pin The installation issue let a tenant-pinned solution with cross_tenant be issued in another organisation while the module mint refused. Both now read ModulePrincipalGrant.IssuedIn; cross_tenant still lets installed authority serve other organisations' installations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): with no envelope, grant from the verified manifest bytes only The registry pass derives the grant from the manifest the signature covers rather than the applied record that matches its digest, and a test holds every unverified case (unsigned, no carrier, no pinned keys, an applied document the signature does not cover) to granting nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(accounts): review r93 repairs; resolve the committed merge markers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts)!: the viewer oracles authenticate the calling module, the viewer travels in the request CheckWorkContextRecordAccess and ListReadableSourceCollections read a viewer Work Context from the header and identified no caller: any workload the mesh let reach the authority port, holding a viewer capability, could ask record access and list readable collections for that viewer (r92 M2). They now authenticate as the delegated exchanges do: the calling module's own Work Context in x-codefly-work-context, held to its current grant and installation, and the viewer in a new required viewer_work_context_token field, which must be addressed to that module (its audience is the module's prefix). A viewer addressed to another module is PermissionDenied ("the viewer Work Context is not addressed to the calling module"). Breaking on the wire: a caller must send its module Work Context in the header and the viewer in the body. The one consumer is the document store (collection_reads.go, publication_access.go); it moves in lockstep. Proto regenerated with codefly 0.1.213 (`generate proto`, `go generate`), contracts with `codefly generate contracts saas-starter`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): the module authority endpoint denies by default The endpoint's interceptor admitted every procedure with no credential and relied on each handler to identify its caller, so a future method that forgot to would be served open to the mesh with every gate green (r92 M3). It now requires exactly one verified module Work Context before any handler runs, for every procedure except the explicit allowlist of workload mints (ModuleAuthorityAuthenticatesInRequest, today MintSourceOperationContext, which proves the module from the projected token in its request). moduleCaller now refuses more than one module Work Context instead of reading the first. TestEveryModuleAuthorityProcedureRefusesAnUnidentifiedCaller builds the endpoint's server (its interceptors, the catalog's registrations) and calls every ModuleAuthorityProcedures entry with no credential and with the internal token alone: each must answer Unauthenticated or PermissionDenied with nothing written. With the interceptor's check removed, the test fails (handlers validate input before identifying the caller and answer InvalidArgument). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(audit): a delegation to a module is granted and revoked on its own events saas.module_delegation.granted and saas.module_delegation.revoked replace saas.work_context.task_started as the audit events Delegate and RevokeDelegation declare. The events contribution, event catalog, AsyncAPI and communication documents are regenerated by the declared composition generator; the RPC catalogs by the repository's go generate steps. ListResources' policy comment no longer calls it unimplemented. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): a delegation fails closed, is the person's own, and ends only its own capabilities r93 B2, M1 and m3: - Delegate refuses, FailedPrecondition by name, on a host that cannot witness a revocation (no policy log), so no delegation is a standing grant nothing can end. - Delegate and RevokeDelegation refuse a resource-confined credential, an identity acting on someone's behalf (X-Act), an impersonated session and any credential that is not an interactive session. - Every hop of a chain under a delegation is sealed to <org>/<module>/<binding>@<delegation>; the binding answer reads the delegation live, so revoking it revokes exactly those capabilities at every verifier. Migration 48 no longer advances the grantor's whole authorization revision. - An organisation administrator may revoke a member's delegation. - A purpose past 1024 characters is InvalidArgument by name. - Grant and revocation are emitted on the audit spine in the transaction that writes the row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(accounts): delegations refuse borrowed credentials, fail closed, and revoke only themselves Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(accounts): delegations as served after r93; remove the committed conflict markers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(accounts): the retained-binding integration asks as the module the child is addressed to TestInstalledContentReadPostgresRetainedBinding presented the exchanged child as the header credential. Under 7cab3a4f the target module asks with its own identity (minted against its current installation) and the child travels in viewer_work_context_token; while the target is uninstalled it has no identity and is refused for that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat(accounts): the journal records the binding and scope set a hop went through Migration 49 adds operation_binding_id and operation (invoke, lookup or headless; both or neither) to actor_chain_journal, folded into the hop's content address when present, with a reader answering them by hop id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): hold each hop to the scope set it was issued under A hop upstream in a chain was compared with the union of its binding's sets, which is invoke, so a lookup hop outlived its binding's lookup. A binding hop now journals its binding and operation, and the chain re-read holds a recorded hop to exactly that set; the remote binding answer runs the same re-read. The re-read is limited to chains sealed to a module binding. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * docs(accounts): a hop is held to the scope set it was issued under Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): a module context the host does not admit is Unauthenticated, not a revocation AuthorizeModuleCapability answered PermissionDenied when it refused the CALLING MODULE itself — a principal no composition registers, installed nowhere for the bound organization, a tenant its grant no longer names, an installation of another registry binding — and FailedPrecondition when the host could not re-read live authority at all. Consumers read PermissionDenied from the revision oracle and the capability surface as "the caller's authority was revoked", so they blamed the person and never re-minted their own context (r103 on document-store #585). Each of those is now Unauthenticated, naming its reason, so the module drops its context and mints a new one; PermissionDenied stays for an admitted module asking beyond its grant (another tenant, an undeclared resource). A host with no live authority read answers Unavailable under LIVE_AUTHORITY_UNAVAILABLE: retryable, and a statement about nobody. The module mint's own 412 for a workload installed nowhere is unchanged. TestAModuleContextThisHostDoesNotAdmitIsUnauthenticated covers each case; the existing assertions move to the new codes; NOTIFICATIONS.md and the accounts AGENTS.md reason table say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(store): renumber the actor-chain hop binding migration above this branch's 51 The hop fixer's 49_actor_chain_hop_binding was numbered against the branch before main's 48 was merged and this branch's migrations moved to 49-51, so it duplicated 49_authority_delivery. It becomes 52; HOST_INTERFACE.md's two references follow, and module_delegation.go's reference to the delegations migration names 51. The SQL is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore(ci): pin codefly 0.1.214; regenerate both generated libraries with it and gate their drift v0.1.214 carries cli#1166 (buf-module client proto sources) and cli#1162 (namespaced TypeScript client barrel exports), so the two checked-in libraries can finally be reproduced by a released CLI instead of a source build: - module/services/frontend/code/packages/saas-sdk/generated: regenerated with `node scripts/generate.mjs --force`. It was stale against accounts/connect since 7cab3a4f moved the contract (the Immutable module package job's TestGeneratedLibraryContractDigestsMatchThePackage), and its barrel came from the unreleased source-df447bbc (r92 m1). - libraries/source-read-sdk: regenerated with the README's command. It was stale against accounts/authority and generated by the unreleased source-2c6225ad (r92 B2, reproducibility half). Its narrowing to ModuleCapabilitiesService still waits on codefly-dev/cli#1173: `--services` narrows only the facade. scripts/ci/check-generated-libraries.sh regenerates both with the pinned CLI and fails on any difference; the SDK-boundary job runs it after the contracts and runnables checks. Contracts, runnables and accounts proto generation are unchanged under 0.1.214. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(accounts): with no envelope, a manifest widening waits for each organisation's consent With no administrative envelope the …
|
Superseded by #1257 (3c53033): child issuance is rule A in the shared authority core, which accounts adopted, with the per-installation binding ceiling of codefly-dev/authority#36. Closed in the 2026-10-27 clean-up on the owner's instruction. |
What
StartTask takes
operation_bindings: binding ids of the audience module. The hostresolves each one the way the module's delegated exchange will
(
ModuleOperationAudience: the binding as installed at the organization, narrowedby that installation). It adds the binding's invoke scopes beside
authority_scopes,keeps each resource only if the person holds it, and refuses by name a binding the
person holds nothing under.
Why
A module that exchanges through a source binding must today name the source ids in
the parent it requests for the person. Those ids are per organization and live in
the installation, so a consumer has to learn them first, and a static configuration
value works for one organization only. With this change the consumer names the
binding, and the ids come from the installation. The parent then carries exactly
what the exchange will redeem.
Classification
Fix at the owner. The installation already holds the narrowing and the exchange
already resolves the binding; the parent's mint now uses that same resolution
instead of each consumer re-deriving it.
Reuse
Service.ModuleOperationAudience: the delegated exchange's binding resolution, unchanged.resolveAuthorityandworkContextScopes: the per-permission check every mint makes.Deliberately not added: a discovery read for consumers to learn source ids, or a new
slot mode in core or the CLI.
Limits
operation_bindings, because their ceilings are decided on caller-named scopes.Verification
New adapter tests cover:
binding's sources, for a person holding nothing under it (by name), and for an
actor's mint.
Each fails with the change reversed. Disabling the expansion fails 4 tests;
disabling deduplication gives a duplicate-scope refusal.
go test ./pkg/business/... ./pkg/adapters/... ./pkg/cataloggen/...passes.Regenerated with the documented pipeline, with the unchanged tree regenerated
clean first:
codefly generate proto(CLI 0.1.185);go generate;codefly generate contractsandrunnables, both clean under--check.The naming gate and
scripts/check-boundaries.share clean.Not verified
needs regenerating first.
codefly ci runwas not run locally; CI covers it.🤖 Generated with Claude Code
https://claude.ai/code/session_012qqzbsVZ4vDaPMecQ4FT8w