Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
2d85773
fix(accounts): a person's capability stays sealed to its audience's i…
saurabhmaheshwari-obin Oct 24, 2026
24fa55d
feat(auth-gateway): a declared module route admits a headless operati…
saurabhmaheshwari-obin Oct 24, 2026
840a391
fix(auth-gateway): the bearer-less arm admits one capability and read…
saurabhmaheshwari-obin Oct 24, 2026
de9a971
fix(accounts): classify every mint by where it seals, and allow-list …
saurabhmaheshwari-obin Oct 24, 2026
fb7d279
contracts: accounts owns installation_listing.proto, and the gateway …
saurabhmaheshwari-obin Oct 24, 2026
f942a7e
fix(auth-gateway): refuse a seal on the alias's module principal, rea…
saurabhmaheshwari-obin Oct 24, 2026
8c34f38
fix(accounts): say which installations carry no registry principal, a…
saurabhmaheshwari-obin Oct 24, 2026
6cef5de
chore(saas-sdk): 0.3.36, for the moved installation listing types
saurabhmaheshwari-obin Oct 24, 2026
c81b616
feat(accounts): AdmitWorkloadOperation, the issuer's allow-list for t…
saurabhmaheshwari-obin Oct 24, 2026
7e0d0b0
fix(auth-gateway): the bearer-less module arm admits what the issuer …
saurabhmaheshwari-obin Oct 24, 2026
c1e2be2
fix(accounts): drop Installation.registry_principal_id, which nothing…
saurabhmaheshwari-obin Oct 24, 2026
bcec7e3
fix(tools): the naming gate reads text files up to 1 MiB
saurabhmaheshwari-obin Oct 24, 2026
da1d474
fix(accounts): AdmitWorkloadOperation reads the capability back and a…
saurabhmaheshwari-obin Oct 24, 2026
1b4c1df
fix(auth-gateway): one issuer call per bearer-less request, no stampe…
saurabhmaheshwari-obin Oct 24, 2026
31f50c9
revert(contracts): installation_listing.proto goes back into installa…
saurabhmaheshwari-obin Oct 24, 2026
23517b2
fix(tools): the naming gate's size refusal names raising the cap for …
saurabhmaheshwari-obin Oct 24, 2026
3b16ae2
docs(accounts): say which refusal a seal the owner no longer holds is
saurabhmaheshwari-obin Oct 24, 2026
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
35 changes: 35 additions & 0 deletions module/GATEWAY_ROUTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,41 @@ correspondence check.
The generated and explicit route catalogs own the complete module REST surface.
Unknown paths return 404. Module workloads cannot add a proxy target at runtime.

A composed module declared in the solution registry is reached at
`/v1/<alias>/*`, only after both catalogs found no route, with the path
forwarded unchanged (`handleDeclaredModule`). An edge-inspected Work Context
there may name the alias or `module-capabilities`; anything else is 401
`invalid work context`. The route then has two arms:

- **The person's bearer path.** ext_authz verifies the bearer and stamps the
person; accounts admits the viewer to the module's installation per request;
the bearer travels (SA-F-BEARER), the cookie never does;
`x-codefly-installation-id` names the target's admitting installation.
- **The bearer-less arm, on the workload endpoint only**, for a headless
operation context. Selected by: no `Authorization` and no `Cookie` header, the
workload listener, and a capability whose audience is the alias. Then: a
presenter when the gateway is deployed; exactly one capability header and a
non-empty signed tenant and seal (else 401 `invalid work context`); every
identity header stripped, `Authorization`, `Proxy-Authorization` and
`X-Forwarded-Client-Cert` dropped; the budget keyed
`workload-operation:<tenant>`, failing closed; then one call to accounts'
`AdmitWorkloadOperation` with the capability, the alias and the route's
target: it must admit the capability as a module operation context for the
alias with the target installed (ACTIVE) in its tenant and the seal none of
the target's rows. Not installed is 403; any other refusal is 401
`authentication required`, the answer a person's capability has without its
bearer. 503 when it cannot answer in time or is not wired, or names another
tenant or seal. A `Connection` option naming the capability is 401 `invalid
work context`. Admitted requests carry the capability and nothing the
gateway asserts: no bearer, no identity header, no
`x-codefly-installation-id`. An upstream failure is a static 502. Every
admit and refusal is logged with alias, tenant, task, seal and presenter,
never the token.

Elsewhere the same request is a bearer-less request: 401 on `rest`, 401 with a
`module-capabilities` capability, 401 for a capability sealed to the target's own
installation.

`saas.frontend.plugins.v1` catalogs all 36 Next.js pages and the admin plugin
catch-all. The environment topology generator owns the deployed ingress
VirtualService, so the target-neutral route catalog does not carry a namespace,
Expand Down
46 changes: 46 additions & 0 deletions module/WORK_CONTEXTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,34 @@ answered by one set of functions for minting and verifying alike
is sealed to the organization's installation of it, held while the person is an
active member; an organization that never installed the audience is minted
nothing. A module's is sealed to the installation its mint resolved.
This is an invariant, not a default: every capability a person's own session
or a module's re-mint for that person issues is sealed to the installation of
the audience it addresses. `StartChildSession` is the one derivation core
cannot re-seal (a child keeps its parent's seal), so it refuses a person's
child for an audience whose installation is not the parent's seal.
Several kinds of person-owned capability are sealed elsewhere, by design, and
each has its own mint gate:
- **Source-operation contexts** (`startSourceOperation`,
`pkg/adapters/source_delegation.go`): owned by the person, sealed to the
installation of the module USING the person's source delegation. Only that
module's identity, exercising a delegation the person granted to one of its
bindings, mints one.
- **Installation tasks** (`mintInstallationTask`,
`pkg/adapters/work_context_rpcs.go`): owned by the installation's owner of
record, sealed to the installation the authority is drawn from. Only an
`EXPOSURE_INTERNAL` credential reaches `StartInstallationTask`, and an
install step is signed by the host itself.
- **Installed binding children** (`exchangeInstalledBinding`,
`pkg/adapters/installed_content_read.go`): a module's `content_read` or
`module_actor` exchange of a person's capability derives a core `Child`,
which keeps the parent's seal (the exchanging module's installation) while
addressing another module.

So a seal on an installation other than the audience's does not mean
unattended work, and nothing decides that from the seal.
`TestAPersonsCapabilityIsSealedToItsAudiencesInstallation` pins the rule over
every person mint, and `TestEveryWorkContextMintIsClassifiedBySeal` fails on
any mint RPC that names neither where it seals nor why.
- **Epoch.** The owner's authorization revision in the organization. Membership
and role changes advance a person's; installing a module and every change to
its installation's revision advance a module principal's (migration 35).
Expand Down Expand Up @@ -635,6 +663,24 @@ A composed module reaches it through the gateway's `POST
/modules/_operation-context` ([services/auth-gateway/AGENTS.md](./services/auth-gateway/AGENTS.md)),
which brokers to accounts exactly as `/modules/_work-context` does.

The minted context is presented at `/v1/<audience>/*` on the same workload
endpoint, with no bearer: the declared module route's bearer-less arm. A
composed workload with no person present, such as a solution's release
publisher, reaches the module it is bound to this way; modules keep calling
their peers in-mesh. The edge checks what core's `Inspector` can (signature,
issuer, window, the route's audience, shape), strips every identity header and
meters on the tenant. It then asks the issuer: accounts'
`AdmitWorkloadOperation` admits a capability only if it is exactly what
`StartModuleOperationTask` signs, owned by a declared module principal other
than the alias's and sealed to that principal's active installation, and
refuses every person-owned kind, including those sealed elsewhere (see the seal
rule above). It reads the capability back against its own live state and
consumes nothing. In the same call it requires the route's target installed in
the signed tenant, with the seal none of its rows. The arm forwards the
capability as presented and stamps nothing beside it. The callee verifies the
capability at full strength, as for any other carrier. The route's rules are in
[GATEWAY_ROUTES.md](./GATEWAY_ROUTES.md#module-rest-routes).

## Operation contexts from a source delegation

A datasource source's sync is neither of the cases above. It runs as a module
Expand Down
Binary file modified module/contracts/api/accounts/authority/contract.binpb
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "buf/validate/validate.proto";
import "google/api/annotations.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
import "saas/accounts/v1/workload_operation_admission.proto";
import "saas/policy/v1/options.proto";

// WorkContextReplayPolicy controls how a consumer may accept a signed Work
Expand Down Expand Up @@ -397,6 +398,32 @@ service WorkContextService {
};
}

// AdmitWorkloadOperation answers whether a capability presented with no
// bearer at the declared module route /v1/<alias>/* is a module operation
// context (StartModuleOperationTask) for that alias, and admits nothing else;
// and whether the route's target is installed in the capability's tenant.
// The auth-gateway's bearer-less arm is its caller. Unlike the revision
// checks above, the signed capability itself crosses this boundary: the
// answer depends on its owner, owner kind and chain, which only the issuer
// reads. It reads the capability back (core's Authority.ReadBack: signature
// under this issuer, window, audience, revision, seal, epochs) and never
// verifies it as presented, so nothing is consumed and the callee still
// verifies at full strength. Internal-tier: the tenant it answers about is
// the one the capability names.
rpc AdmitWorkloadOperation(AdmitWorkloadOperationRequest) returns (AdmitWorkloadOperationResponse) {
option (saas.policy.v1.method_policy) = {
exposure: EXPOSURE_INTERNAL
tenant: TENANT_REQUIREMENT_NONE
mfa: MFA_REQUIREMENT_NONE
audit: {emission: AUDIT_EMISSION_NONE}
idempotency: IDEMPOTENCY_REQUIREMENT_FORBIDDEN
rate_limit: RATE_LIMIT_CLASS_INTERNAL
request_sensitivity: SENSITIVITY_CONFIDENTIAL
response_sensitivity: SENSITIVITY_CONFIDENTIAL
platform_role: PLATFORM_ROLE_REQUIREMENT_NONE
};
}

rpc StartTask(StartTaskWorkContextRequest) returns (IssuedWorkContext) {
option (saas.policy.v1.method_policy) = {
exposure: EXPOSURE_AUTHENTICATED
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
syntax = "proto3";

package saas.accounts.v1;

import "buf/validate/validate.proto";

// Workload operation admission: the issuer's answer to whether a capability
// presented with no bearer at a declared module route is a module operation
// context, the one shape that route admits without a person.
//
// A forwarding hop cannot answer it. Core's Inspector, which is all a hop runs,
// withholds the owner, the owner's kind, the actor chain and the operation
// binding, and those are exactly what tell a module's unattended work from a
// person's capability. Accounts signed the capability and reads it back, held
// against its own live state, so the gateway asks it, and asks in the same call
// whether the route's target is installed. WorkContextService/AdmitWorkloadOperation
// takes and answers these messages.
//
// They live in their own file, with deliberately minimal imports, so the
// auth-gateway can generate the same types from the same source proto, as it
// does for module_registration.proto and solution_entitlements.proto. The
// gateway's copy is this file, byte for byte.

// WorkloadOperationRefusal is why a capability was not admitted. The gateway
// logs it and answers every refusal alike.
enum WorkloadOperationRefusal {
WORKLOAD_OPERATION_REFUSAL_UNSPECIFIED = 0;
// Not a capability this issuer signed, inside its window, addressed to the
// alias; or the alias is one no operation context is minted for.
WORKLOAD_OPERATION_REFUSAL_NOT_INSPECTED = 1;
// Owned by a principal that is not a service: a person, an agent, or a kind
// this issuer does not name.
WORKLOAD_OPERATION_REFUSAL_OWNER_NOT_SERVICE = 2;
// A service principal's capability that is not shaped as a module operation
// context: it carries an actor chain, a grant hop, an operation binding, a
// parent session or an owner agent, names no authority scope, or has another
// replay policy or a longer window.
WORKLOAD_OPERATION_REFUSAL_NOT_OPERATION_CONTEXT = 3;
// Owned by the module principal the alias names: a module does not reach
// itself through this route.
WORKLOAD_OPERATION_REFUSAL_OWNER_IS_TARGET = 4;
// The owner is not a declared module principal, so no installation of its
// can stand behind the seal.
WORKLOAD_OPERATION_REFUSAL_SEAL_NOT_OWNERS = 5;
// The tenant has no ACTIVE installation of the route's target. The gateway
// answers this one 403, as it answers a person whose organization never
// installed the module.
WORKLOAD_OPERATION_REFUSAL_TARGET_NOT_INSTALLED = 6;
// The seal is one of the target's own installations: the caller would be the
// module it addresses.
WORKLOAD_OPERATION_REFUSAL_SEAL_ON_TARGET = 7;
// The issuer's live state has moved past the capability: its authorization
// revision is superseded, an epoch it carries has advanced, the seal names an
// installation the owner no longer holds, or the owner's module was
// uninstalled or now belongs to another tenant.
WORKLOAD_OPERATION_REFUSAL_NOT_CURRENT = 8;
}

message AdmitWorkloadOperationRequest {
// The capability as presented, encoded. It is read back by its issuer, never
// verified as presented: nothing it carries is consumed.
string work_context = 1 [
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.max_len = 32768
];
// The route alias the gateway resolved from the request path. The capability
// must be addressed to it.
string alias = 2 [
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.max_len = 512
];
// The immutable solution target the route's alias resolved to, from the
// gateway's own routing. The tenant must have an ACTIVE installation of it.
string target_id = 3 [(buf.validate.field).string.uuid = true];
}

message AdmitWorkloadOperationResponse {
bool admitted = 1;
// Set when admitted is false.
WorkloadOperationRefusal refusal = 2;
// The signed facts the gateway logs and forwards, set only when admitted:
// the tenant, the installation the capability is sealed to (the CALLING
// module's), and the task.
string tenant_id = 3;
string installation_id = 4;
string task_id = 5;
}
Binary file modified module/contracts/api/accounts/connect/contract.binpb
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "buf/validate/validate.proto";
import "google/api/annotations.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
import "saas/accounts/v1/workload_operation_admission.proto";
import "saas/policy/v1/options.proto";

// WorkContextReplayPolicy controls how a consumer may accept a signed Work
Expand Down Expand Up @@ -397,6 +398,32 @@ service WorkContextService {
};
}

// AdmitWorkloadOperation answers whether a capability presented with no
// bearer at the declared module route /v1/<alias>/* is a module operation
// context (StartModuleOperationTask) for that alias, and admits nothing else;
// and whether the route's target is installed in the capability's tenant.
// The auth-gateway's bearer-less arm is its caller. Unlike the revision
// checks above, the signed capability itself crosses this boundary: the
// answer depends on its owner, owner kind and chain, which only the issuer
// reads. It reads the capability back (core's Authority.ReadBack: signature
// under this issuer, window, audience, revision, seal, epochs) and never
// verifies it as presented, so nothing is consumed and the callee still
// verifies at full strength. Internal-tier: the tenant it answers about is
// the one the capability names.
rpc AdmitWorkloadOperation(AdmitWorkloadOperationRequest) returns (AdmitWorkloadOperationResponse) {
option (saas.policy.v1.method_policy) = {
exposure: EXPOSURE_INTERNAL
tenant: TENANT_REQUIREMENT_NONE
mfa: MFA_REQUIREMENT_NONE
audit: {emission: AUDIT_EMISSION_NONE}
idempotency: IDEMPOTENCY_REQUIREMENT_FORBIDDEN
rate_limit: RATE_LIMIT_CLASS_INTERNAL
request_sensitivity: SENSITIVITY_CONFIDENTIAL
response_sensitivity: SENSITIVITY_CONFIDENTIAL
platform_role: PLATFORM_ROLE_REQUIREMENT_NONE
};
}

rpc StartTask(StartTaskWorkContextRequest) returns (IssuedWorkContext) {
option (saas.policy.v1.method_policy) = {
exposure: EXPOSURE_AUTHENTICATED
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
syntax = "proto3";

package saas.accounts.v1;

import "buf/validate/validate.proto";

// Workload operation admission: the issuer's answer to whether a capability
// presented with no bearer at a declared module route is a module operation
// context, the one shape that route admits without a person.
//
// A forwarding hop cannot answer it. Core's Inspector, which is all a hop runs,
// withholds the owner, the owner's kind, the actor chain and the operation
// binding, and those are exactly what tell a module's unattended work from a
// person's capability. Accounts signed the capability and reads it back, held
// against its own live state, so the gateway asks it, and asks in the same call
// whether the route's target is installed. WorkContextService/AdmitWorkloadOperation
// takes and answers these messages.
//
// They live in their own file, with deliberately minimal imports, so the
// auth-gateway can generate the same types from the same source proto, as it
// does for module_registration.proto and solution_entitlements.proto. The
// gateway's copy is this file, byte for byte.

// WorkloadOperationRefusal is why a capability was not admitted. The gateway
// logs it and answers every refusal alike.
enum WorkloadOperationRefusal {
WORKLOAD_OPERATION_REFUSAL_UNSPECIFIED = 0;
// Not a capability this issuer signed, inside its window, addressed to the
// alias; or the alias is one no operation context is minted for.
WORKLOAD_OPERATION_REFUSAL_NOT_INSPECTED = 1;
// Owned by a principal that is not a service: a person, an agent, or a kind
// this issuer does not name.
WORKLOAD_OPERATION_REFUSAL_OWNER_NOT_SERVICE = 2;
// A service principal's capability that is not shaped as a module operation
// context: it carries an actor chain, a grant hop, an operation binding, a
// parent session or an owner agent, names no authority scope, or has another
// replay policy or a longer window.
WORKLOAD_OPERATION_REFUSAL_NOT_OPERATION_CONTEXT = 3;
// Owned by the module principal the alias names: a module does not reach
// itself through this route.
WORKLOAD_OPERATION_REFUSAL_OWNER_IS_TARGET = 4;
// The owner is not a declared module principal, so no installation of its
// can stand behind the seal.
WORKLOAD_OPERATION_REFUSAL_SEAL_NOT_OWNERS = 5;
// The tenant has no ACTIVE installation of the route's target. The gateway
// answers this one 403, as it answers a person whose organization never
// installed the module.
WORKLOAD_OPERATION_REFUSAL_TARGET_NOT_INSTALLED = 6;
// The seal is one of the target's own installations: the caller would be the
// module it addresses.
WORKLOAD_OPERATION_REFUSAL_SEAL_ON_TARGET = 7;
// The issuer's live state has moved past the capability: its authorization
// revision is superseded, an epoch it carries has advanced, the seal names an
// installation the owner no longer holds, or the owner's module was
// uninstalled or now belongs to another tenant.
WORKLOAD_OPERATION_REFUSAL_NOT_CURRENT = 8;
}

message AdmitWorkloadOperationRequest {
// The capability as presented, encoded. It is read back by its issuer, never
// verified as presented: nothing it carries is consumed.
string work_context = 1 [
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.max_len = 32768
];
// The route alias the gateway resolved from the request path. The capability
// must be addressed to it.
string alias = 2 [
(buf.validate.field).string.min_len = 1,
(buf.validate.field).string.max_len = 512
];
// The immutable solution target the route's alias resolved to, from the
// gateway's own routing. The tenant must have an ACTIVE installation of it.
string target_id = 3 [(buf.validate.field).string.uuid = true];
}

message AdmitWorkloadOperationResponse {
bool admitted = 1;
// Set when admitted is false.
WorkloadOperationRefusal refusal = 2;
// The signed facts the gateway logs and forwards, set only when admitted:
// the tenant, the installation the capability is sealed to (the CALLING
// module's), and the task.
string tenant_id = 3;
string installation_id = 4;
string task_id = 5;
}
Binary file modified module/contracts/api/auth-gateway/grpc/contract.binpb
Binary file not shown.
Loading
Loading