Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ agent clients.
**The runtime runs only under the authority.** The saas-host mode — a bearer
forwarded to the host's gateway, `ForModule` minting a Task Work Context
through accounts, the broker execution credential, `Transcoded`/`Unary`
calls over a module's REST binding, the install step, gateway-stamped
identity headers and the MCP surface's OAuth discovery against the host's
issuer — is **deleted, not fenced**. A boot whose composition names no
calls over a module's REST binding, the install step verified from its
carrier alone, gateway-stamped identity headers and the MCP surface's OAuth
discovery against the host's issuer — is **deleted, not fenced**. The install
step is back under the authority (`installation.go`). The authority's `Check`
at `POST /_codefly/installations` is its dedicated step verifier. A step runs
only after that check, and only when the body names exactly what was verified.
It is given no Gateway, and a solution with no `OnInstall` is never
acknowledged. Do not let a remembered acknowledgement answer before the check,
and do not hand the step anything to act under. A boot whose composition names no
`authority/endpoint` is refused. If you are about to add a path that reaches a
module, or learns who a viewer is, without the authority, you are rebuilding
what this repository deleted.
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# Changelog

## Unreleased — the install step, verified by the authority

- **`OnInstall`, `Installation`, `RefuseInstall`, `ErrInstallRefused`** are back,
additive to the current API. The host delivers a pending installation's setup
to `POST /_codefly/installations`, whose body is exactly
`{installation_id, organization_id, revision}`.
The runtime asks the authority's `Check` at that method, which is the
authority's dedicated, never-advisory step verifier. The body and the
installation carriers must then name exactly the organization, installation
and revision it verified, with a consent operation and only
`installations:install` on that installation.
- `Installation` is the verified facts, with the new `OperationID` (the consent
operation, the verified task id). An authority refusal answers 409 with one
generic reason, as does a refusal answered beside the verified context. A body carrying
any other member, `scope` included, is refused 400.
- The step is given no Gateway, and a solution with no step is answered 409,
never 204.
- One (installation, revision, consent operation) runs once per process. A repeat is
verified before a remembered 204 answers it.
- What the e80369a deletion removed stays removed: a carrier parsed alone, a
nil step acknowledged, and a step acting under the carried capability.

## Unreleased — an operation names the tool it is offered as

- **`Operation.Tool`** is the model-facing name of a marked read. Empty keeps
Expand Down
64 changes: 62 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,14 @@ the host's gateway, a Task Work Context minted through accounts for the viewer
from the host's module broker (`Server.Credential`, `CredentialSource`, the
`module-authority` group), a module called over its REST binding
(`Gateway.Transcoded`, `Unary`, `HTTPClient`, `BaseURL`), the install step
(`OnInstall`, `Installation`, `RefuseInstall`), the identity headers the
the host verified itself from a carried capability, the identity headers the
gateway stamped (`InstallationID`, `x-org-id`, `x-session-id`), and the MCP
surface's OAuth discovery against the host's issuer. All of it is **deleted,
not fenced**: a solution that consumes modules relays them only through the
authority, and no path of the saas mode was still reachable. A composition
that names no `authority/endpoint` is refused at boot. Nothing bridges the two
models.
models. The install step came back under the authority
([below](#the-install-step)): the authority verifies it, not the carrier.

## A runtime does not register itself

Expand Down Expand Up @@ -179,6 +180,65 @@ the projected token at `/var/run/secrets/codefly/token` (overridden by
is rotated under the running process. Declare `module-identity` on the
backend so the run projects it.

### The install step

The host records an installation **pending** and delivers its setup to
`POST /_codefly/installations` on this listener. The installation becomes
active only after this answers 204 for it, and `OnInstall(step)` is what
answers. The request carries a **setup-only credential** that the authority
issued for that one pending attempt, in `x-codefly-work-context`, with
`x-codefly-installation-id` and `x-codefly-installation-revision` beside it. Its
body is exactly `{installation_id, organization_id, revision}`; any other member,
a `scope` among them, is refused 400.

- **The authority verifies the step, and nothing else does.** The runtime asks
the authority's `Check` at the method `POST /_codefly/installations`. The
authority routes that method to its dedicated step verifier and never to
ordinary checking, and the check is never advisory, under
`hop/ENFORCEMENT=warn` too. The verifier holds the credential to this
module's delivered principal, the installation and its revision, the
delivered generation, and the current pending attempt. The runtime then
requires the body and the carriers to name exactly what was verified: the
organization, installation and revision. It also requires a consent
operation and a single scope, `installations:install` on that installation. A carrier
parsed alone is never enough.
- **`Installation` is what was verified**: `ID`, `OrganizationID`, `Revision`,
and `OperationID`, the consent operation the step was issued under (the
verified task id), which a host retry of the same attempt keeps. The rest of
the consent receipt (its intent, the delivered generation and the offer
digest) is the authority's to check, through the step's signed binding and
its current state. Nothing else is passed on.
- **The step holds nothing.** It is given no Gateway (`ViewerFromContext`
errors inside it), and the setup credential is never handed to it or
forwarded. A pending installation is active nowhere, so there is nothing to
act under until it is.
- **One attempt runs once per process.** Concurrent deliveries of one
(installation, revision, consent operation) wait for the first, and an
acknowledgement is answered from memory for the most recent 1024 attempts.
A repeat is still verified by the authority first, so a remembered 204 is
never the answer to an attempt that no longer stands. A refusal and a
failure are not remembered, so the host's retry runs the step again. The
memory is per process: across a restart or another replica the step runs
again, so it must converge.
- **Answers.** 204 is done. 409 `{"reason": …}` ends this delivery:
- `RefuseInstall`'s public reason;
- the authority refusing the credential (PermissionDenied, InvalidArgument,
Unauthenticated, NotFound, or a refusal answered beside the context), with
one generic reason, the authority's own being logged;
- a verified step that names another installation;
- a solution that declares no step (with no step, nothing is ever
acknowledged).

A 409 does not end the installation: a fresh claim or a fresh consent may
issue another credential. 401 means no single credential was carried. 400
means a malformed body or carriers. 503 covers everything the host should
retry, the installation staying pending: the attempt changed or its
credential is outside its window (FailedPrecondition), the call was cancelled
or timed out, the authority failed or is unreachable, or the step failed. A
step's own error text never reaches the answer. Verification is admission,
not a reservation: the authority's activation fence still refuses a revoke
or redelivery that races the step.

## Configuration

On boot `Serve` calls `codefly.Init(ctx)`, which loads Codefly's injected
Expand Down
11 changes: 10 additions & 1 deletion authority_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ type interfaceAuthority struct {
// method no row matches is refused with the authority's reason, or, on an
// advisory check, answered beside the context.
held []string
// step, when set, answers a Check at the install step's method: the
// authority's dedicated step verifier.
step func(*authorityv1.CheckRequest) (*authorityv1.CheckResponse, error)
}

// edgeContext is the person's Work Context the edge sends the page.
Expand All @@ -60,8 +63,14 @@ const robinChild = "robin-child-for-the-page"
func (a *interfaceAuthority) Check(_ context.Context, r *authorityv1.CheckRequest) (*authorityv1.CheckResponse, error) {
a.mu.Lock()
a.checked = append(a.checked, proto.Clone(r).(*authorityv1.CheckRequest))
held := a.held
held, step := a.held, a.step
a.mu.Unlock()
if r.GetMethod() == installationStepMethod {
if step == nil {
return nil, status.Error(codes.PermissionDenied, "not this installation's setup credential")
}
return step(r)
}
if r.GetToken() == malformedContext {
return nil, status.Error(codes.InvalidArgument, "the Work Context is not a token the authority can read")
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.27.3

require (
connectrpc.com/connect v1.21.0
github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261027012935-f41cc2baa2ec
github.com/codefly-dev/authority/libraries/authority-client/go v0.1.1-0.20261029092314-21ce6d9284ed
github.com/codefly-dev/authority/libraries/hop/go v0.0.0-20261027012935-f41cc2baa2ec
github.com/codefly-dev/core v0.19.7-0.20261026182446-518714d046af
github.com/codefly-dev/sdk-go v0.4.1-0.20261018052022-70dc7a546458
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ github.com/cheggaaa/pb/v3 v3.2.1 h1:aprZbFRG+B7+ug76S8QZ6Y1PW168UHzOmbC3wa+aU6I=
github.com/cheggaaa/pb/v3 v3.2.1/go.mod h1:U9hSVxoKqJrZIE3PkFG1xXXNaK/Ilzg+EF/scpXzEdw=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261027012935-f41cc2baa2ec h1:gjLHn5hsteMDx8TeNj+KPBsa06nLwzAEhyR2IfC3JWs=
github.com/codefly-dev/authority/libraries/authority-client/go v0.0.0-20261027012935-f41cc2baa2ec/go.mod h1:4hRjiWuJOKzrEk8AS87pbWCbpiEmhEvQBGQaJo5Hmkg=
github.com/codefly-dev/authority/libraries/authority-client/go v0.1.1-0.20261029092314-21ce6d9284ed h1:6dZKwVVpyiCIg25j32K6WGO72F8yHUKHvNbmYAt8Pq0=
github.com/codefly-dev/authority/libraries/authority-client/go v0.1.1-0.20261029092314-21ce6d9284ed/go.mod h1:4hRjiWuJOKzrEk8AS87pbWCbpiEmhEvQBGQaJo5Hmkg=
github.com/codefly-dev/authority/libraries/hop/go v0.0.0-20261027012935-f41cc2baa2ec h1:QhncdaWaMERAGpZthlSXjKroXNl/L8llPtBMX+e5w/A=
github.com/codefly-dev/authority/libraries/hop/go v0.0.0-20261027012935-f41cc2baa2ec/go.mod h1:NqCbvIWbkbCzSpUUYQjP8Jv+LpFnDRbVhPdAj67DHhE=
github.com/codefly-dev/core v0.19.7-0.20261026182446-518714d046af h1:JLTecMs4Oja+fl51chLyGw262S4yz7jzEEiFxoq7Viw=
Expand Down
Loading
Loading