Skip to content
Closed
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
49 changes: 42 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

### Added

- **`moduleauthority.Client.ListExecutableArtifactSources`** observes one
effective executable-artifact policy's complete source set for one exact
current installation. It preserves the independent parent and module proofs,
checks the returned installation/policy association and bounded set, and
creates no approval or activation. Callers explicitly select/check a source
and compare the returned tenant with their independently verified tenant.

- **`moduleauthority.WorkContextSources`**, the three live-authority sources a
callee's verifier needs, over
`ModuleCapabilitiesService/ResolveWorkContextAuthority`
Expand Down Expand Up @@ -36,6 +43,18 @@
`CodeFailedPrecondition` ("the issuer decided; mint again") from
`CodeUnavailable` ("the lookup failed; retry").

- `moduleauthority.Client.MintModuleWorkContext(ctx)` returns
`ModuleContext{Token, ExpiresAt}` without caching, coalescing or retrying.
`ExpiresAt` is the broker's reported expiry, not a decoded token claim.
Existing `ModuleWorkContext(ctx) (string, error)` retains its cache and
shared refresh through the same mint implementation; organization binding,
bounded reads, Core-backed sealed-shape checking and safe refusals are shared.
- `BrokerTransportConnectionReset` classifies wrapped `syscall.ECONNRESET`
through error identity with a fixed diagnostic, retaining no underlying
cause. It is appended so existing transport category values remain stable;
callers can choose to retry without accessing sensitive transport errors.


- **`moduleauthority.WorkloadTokenFile()`**, the one answer to "where is this
module's projected token": the file `CODEFLY__WORKLOAD_TOKEN_FILE` names
when it is set to a non-blank value, and the pod mount
Expand All @@ -54,11 +73,14 @@

### Changed

- **`gen/` moves from module-saas-starter v0.0.94 to `9ab36e7e`** (the merge
commit of #1119 on `main`), which is where `ResolveWorkContextAuthority`
first exists. It carries every accounts contract change made since v0.0.94 —
`datasource`, `installations`, `api_keys`, `module_registration` and
`work_contexts` all move — and those changes are the host's, not this SDK's.
- Reconcile the existing Core/uncached-mint owner work with the selected host: Core `v0.19.6-0.20261018171306-0a8d16c5e32d` and gRPC v1.84.0. This is an exact development dependency, not a release-qualified floor. Core owns workload-build checks at mint and revocation through epochs/revisions; this SDK no longer asks for a per-principal build.

- **`gen/` moves from module-saas-starter v0.0.94 through `9ab36e7e` to
`f9321b94`**, the exact development head of #1195. It carries every accounts
contract change across those refs, including datasource/installations,
delivered solution presence, and the module source-policy observation.
`ResolveWorkContextAuthority` first appeared at the intermediate #1119 merge.
These are host contract changes, not a host release or SDK publication.

- **`moduleauthority.ErrNoHostStateToMintFor`**, for HTTP **412** on the module
Work Context and operation-context mints. The gateway at this ref relays
Expand All @@ -73,15 +95,28 @@

### Removed (breaking)

- **`WorkContextSources.ApprovedBuild`** is removed with Core's corresponding `SealSource` requirement and `ErrNoApprovedBuild`. Consumers must use the current Core verifier interfaces; no replacement local verifier or absence sentinel is introduced.

- **`SessionId` is gone from three Work Context mint requests**, with their
`GetSessionId()` accessors: `StartTaskWorkContextRequest`,
`StartChildSessionWorkContextRequest` and `StartInstallationTaskRequest`. The
host stopped accepting a caller-named session — every session a capability
carries is now the one its caller authenticated with, or a fresh one the
issuer assigns and returns — so there is no replacement field and nothing to
set. A consumer that set one was naming a session it did not own; it drops the
assignment. These are the only exported symbols the regeneration removed:
nothing else in `gen/` lost a type, a field or an RPC.
assignment.

- **`ModuleMintWorkContextRequest.Upstream` / `Manifest` and their getters**
are removed. Work-context renewal no longer publishes delivery or frontend
presence; the host's applied delivery contracts own that lifecycle.

- **`SolutionBackendBinding`** and its `Revision`, `ServiceAlias`, `Upstream`
and `ContractVersion` fields/getters are removed, together with
**`SolutionRegistration.Backend` / `GetBackend`** and
**`SolutionFrontendBinding.ContractVersion` / `GetContractVersion`**.
Consumers migrate to the host's delivered binding/target registration and
frontend-generation contracts; there is no SDK compatibility shim for the
removed mutable backend registration.

## v0.1.2 — core v0.19.1; Work Contexts are core's, carried opaque

Expand Down
96 changes: 76 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ The SDK surfaces are:

This call surface uses the four host RPCs from
[module-saas-starter#1052](https://github.com/codefly-dev/module-saas-starter/pull/1052).
`SOURCE.txt` records the released host tag `v0.0.94` used to regenerate
`SOURCE.txt` records the exact development host revision used to regenerate
the bindings. This regeneration does not publish an SDK release.
The host's runtime-scheduled `PruneSourceOperationReceipts` and
`LookupPruneSourceOperationReceipts` are included in the generated bindings
Expand All @@ -242,8 +242,35 @@ The SDK surfaces are:
`ErrInvalidCapability`; a `Parent` core does not decode is refused as invalid
input (`ErrInvalidExchange`, `ErrInvalidArtifact`, `ErrInvalidInstallation`)
before anything is sent. **This needs a host whose accounts mints core-format
Work Contexts** (module-saas-starter#1093); the v0.0.94 host recorded in
`SOURCE.txt` mints the pre-cutover format, and this client refuses it.
Work Contexts** (module-saas-starter#1093); the former v0.0.94 source mints
the pre-cutover format, which this client refuses. `SOURCE.txt` records
the exact selected development host revision.

The broker gateway is the host's resolved **workload** endpoint; its public
REST endpoint refuses these paths. The projected workload token proves the
caller. Operation/source exchanges also require their perimeter credential.

`Client.ModuleWorkContext(ctx)` returns a cached token, sharing refreshes
between concurrent callers. Consumers that already own their expiry and
refresh controller use `Client.MintModuleWorkContext(ctx)` instead:

```go
issued, err := authority.MintModuleWorkContext(ctx)
if err != nil {
return err
}
// issued.Token is opaque; issued.ExpiresAt is the broker's reported expiry.
```

This returns `ModuleContext{Token string, ExpiresAt time.Time}` and mints
once per call. It neither reads nor updates the cached token, coalesces
concurrent calls, nor retries failures. Both methods use the same broker
request, bounded response reader, expiry parser and refusal mapping. The
projected credential is reread for each mint; the organization remains the
client's `Credentials.OrganizationID`, so keep a client per organization.
The returned expiry comes from the broker, never from decoded token claims.
It does not prove authority or override a verifier's lifetime limit; Core
owns authority validation, and consumers own their refresh/deadline policy.

It talks to the two seams the host serves a composed module, and takes both
explicitly — the consumer passes what Codefly resolved, nothing is defaulted:
Expand Down Expand Up @@ -312,7 +339,7 @@ The SDK surfaces are:
sends exactly one `Authorization: Bearer <token>` header alongside the
internal perimeter token; the removed module-secret credential has no fallback.

The REST bodies match [the v0.0.94 gateway](https://github.com/codefly-dev/module-saas-starter/blob/v0.0.94/module/services/auth-gateway/code/gateway_modules.go):
The REST bodies match [the v0.0.97 gateway](https://github.com/codefly-dev/module-saas-starter/blob/v0.0.97/module/services/auth-gateway/code/gateway_modules.go):

| Mint path | JSON fields sent |
| --- | --- |
Expand Down Expand Up @@ -449,8 +476,8 @@ The proto source of truth is `module-saas-starter/module/services/accounts/proto
To refresh from a released tag (in a checkout of that repo):

```bash
git fetch origin tag v0.0.94
git checkout --detach v0.0.94
git fetch origin tag v0.0.97
git checkout --detach v0.0.97
cd module/services/accounts/proto
buf generate --template <this-repo>/buf.gen.yaml -o <this-repo>
```
Expand All @@ -465,9 +492,12 @@ The template generates the complete host-owned `saas/` schema tree. Its
vendored `codefly/` import closure is compiled for Runnable annotations, with
imports resolved to `github.com/codefly-dev/core/generated/go`; Core owns
those Go descriptors. Emitting a second copy in this SDK would conflict at
protobuf initialization. Core v0.19.1 supplies the required Runnable package;
the SDK need not adopt the host's entire dependency generation. Verify the
compatible dependency floor with SDK and consumer builds when refreshing it.
protobuf initialization. The SDK selects the exact current-host Core candidate recorded in `go.mod`,
which supplies the
required Runnable package and keeps the Kubernetes dependency graph compatible
with stable client-go's structured-merge-diff/v6. The SDK need not adopt the
host's entire dependency generation. Verify the compatible dependency floor
with SDK and consumer builds when refreshing it.
The generation plugin versions remain pinned.
Record the release tag in `SOURCE.txt`; CI checks out that tag through
`refs/tags/` and verifies that regeneration leaves no diff. Before a host
Expand Down Expand Up @@ -539,6 +569,22 @@ or binding is required. It checks current parent authority and delegation,
authenticated module/audience/tenant alignment, current membership, and exact
active record identity. A refusal never invokes a bearer-based fallback.

### Sources permitted by an executable-artifact policy

`Client.ListExecutableArtifactSources(ctx, ExecutableArtifactSourcesRequest)`
reads one effective `PolicyID` for the selected `InstallationID`, with the
original signed `Parent` and the module's independent authority credential.
It returns `ExecutableArtifactSources{Installation, PolicyID, Sources}` and
requires the exact policy/installation association and bounded complete set.

Compare `Installation.TenantID` to the independently verified request tenant.
Handle zero, one and many sources explicitly; choose or check the intended source
using the consuming module's syntax and reserved-name rules. No source is
preferred or exclusively owned. The set is member-visible metadata, not content
access, publication authority, executable approval or an activation command.
Later actions must recheck their own authority. A refusal never broadens authority
or falls back to a viewer bearer.

### Verifying a Work Context presented to you, at full strength

A module that RECEIVES a Work Context is its callee, and a callee verifies with
Expand Down Expand Up @@ -574,7 +620,7 @@ same capability keeps one.

What the answers mean:

- `corework.ErrNoSeal`, `ErrNoApprovedBuild` and `ErrNoBinding` are core's own
- `corework.ErrNoSeal` and `ErrNoBinding` are core's own
sentinels, which its verifier turns into a refusal of the capability.
- `ErrUnreadableAuthorityAnswer` is a host answer this client will not read — a
state it does not understand, another arm, or an answer about another
Expand All @@ -589,10 +635,16 @@ What the answers mean:
Positive answers are cached for at most `AuthorityCacheBound` (2 seconds), which
is a **ceiling**: a cached answer delays a revocation by exactly its bound, so a
larger one is refused rather than clamped, and `CacheEntries: -1` turns caching
off. Refusals are never cached. One verification costs five or more round trips
(the revision, the seal, an epoch and a build for the owner and for each actor
hop, a binding), so decide the cadence for a stream deliberately rather than
discovering it from a latency graph.
off. Refusals are never cached. Verification reads the revision, installation
seal, principal epochs and any operation binding or approval grants it names.
Core no longer asks callees to resolve an approved build per principal: the
host checks attested workload builds at mint and revokes changed authority
through principal epochs and installation revisions.
Consumers composing this Core candidate must also select a compatible callee
verifier: the selected host uses `sdk-go/workcontext`
`v0.1.1-0.20261018052022-70dc7a546458` (and root `sdk-go`
`v0.4.1-0.20261018052022-70dc7a546458`). Released Work Context v0.1.0
references removed Core seal fields and does not compile with this selection.

This module verifies nothing: it holds no key, reads no signature and decides
nothing about a capability. The decision is core's, through
Expand Down Expand Up @@ -660,15 +712,19 @@ reconnect it — minting again will not help. The gateway answers 412 there only
for a missing delegation, so the status alone decides it. Body-based delegation
distinctions are gone: source 403s all return `ErrDelegationInvalid`, whether
the delegation was revoked or is otherwise unusable.
At host v0.0.97, a module work-context mint can also answer 412 when the
workload is admitted but installed nowhere. That remains a `BrokerError`
with `StatusCode == 412` and `BrokerErrorOther`, without a delegation sentinel;
the module can serve its install endpoint while waiting for installation.
`BrokerError.Path` and `BrokerError.Reason` have been removed.

Transport and response read failures return `*moduleauthority.BrokerTransportError`
with a fixed message and a `Category`: `BrokerTransportTimeout`,
`BrokerTransportConnectionRefused`, `BrokerTransportTLS`, or
`BrokerTransportOther`. It retains no underlying error and has no `Unwrap`
method. Callers use `errors.As` and the category for retry decisions, and check
`BrokerTransportConnectionRefused`, `BrokerTransportConnectionReset`,
`BrokerTransportTLS`, or `BrokerTransportOther`. It retains no underlying
error and has no `Unwrap` method. Callers use `errors.As` and the category for retry decisions, and check
their context's `Err()` for cancellation. For example, a caller may retry a 502,
503, timeout or refused connection, while surfacing a 401/403 denial. The SDK
does not automatically retry broker failures. Malformed initial HTTP headers
provide no usable status and produce a transport error; malformed bodies cannot
503, timeout, refused connection or connection reset, while surfacing a 401/403
denial. The SDK does not automatically retry broker failures. Malformed initial
HTTP headers provide no usable status and produce a transport error; malformed bodies cannot
change a successfully parsed non-200 status into a transport failure.
13 changes: 6 additions & 7 deletions SOURCE.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
gen/ generated from codefly-dev/module-saas-starter@9ab36e7efff8cfd15d48f319dfa540e52c630725
gen/ generated from codefly-dev/module-saas-starter@f9321b94d91eb91252e7611d571d5bf99cca370a
(accounts public proto: module/services/accounts/proto)
(auth gateway: module/services/auth-gateway/code)

module version: 0.1.0 (module/module.package.codefly.yaml)
This SDK's release is matched to the saas-starter module version above.

Released upstream tag: none — this is the merge commit of
codefly-dev/module-saas-starter#1119 on `main`, which is where
ResolveWorkContextAuthority first exists. A commit is a ref this repository's
gen-drift job resolves, so the SDK does not wait on a deploy tag to carry a
contract its consumers need; the next regeneration moves to whichever tag
overtakes it.
Released upstream tag: none — this is the exact development head of
codefly-dev/module-saas-starter#1195. It adds the policy-scoped current
installation source observation. The candidate is not a released host contract.
The gen-drift and host-gateway-conformance jobs resolve this exact commit;
the next regeneration moves to whichever accepted release overtakes it.
Regeneration does not publish an SDK release.
10 changes: 9 additions & 1 deletion conformance/hostgateway/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,22 @@ The script fails, rather than passes, when:

## What it holds

The three mints accepted, and exactly `prefix`, `service_account_token`,
The three mints on the production workload listener, public-socket refusal,
work minting under its projected token and the retained operation/source
perimeter checks, and exactly `prefix`, `service_account_token`,
`organization_id`, `binding` and `source_id` reaching accounts; each accounts
refusal → gateway status → SDK sentinel; a pre-cutover token relayed and
refused as `ErrInvalidCapability`; the legacy header, a missing,
duplicate, non-Bearer or empty bearer refused before accounts; the 64 KiB
token bound; and the three gateway answers the fake copies (D1, D3, D4 in
`moduleauthority/host_fake_test.go`).

At host v0.0.97 (`640ee503617a0130abdba79d8eeb11dc4d8be723`), a work-context
mint's `FailedPrecondition` maps to HTTP 412 (installation pending), preserved
as `BrokerError` without a delegation sentinel. This differs from the source
mint's 412 (`ErrDelegationMissing`). The host's `gateway_modules.go` and
`TestGateway_ModuleWorkContext_RelaysAccountsOutcome` define that mapping.

## When it goes red

First ask which kind of red it is. Two kinds look alike after a `SOURCE.txt`
Expand Down
Loading
Loading