Skip to content
Merged
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
20 changes: 10 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ jobs:
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
if: steps.migration-reference.outputs.replay == 'true'
with:
go-version: "1.27.2"
go-version: "1.27.3"
cache-dependency-path: module/services/store/code/go.sum
- name: Verify migration upgrade and clean install on PostgreSQL
if: steps.migration-reference.outputs.replay == 'true'
Expand Down Expand Up @@ -246,7 +246,7 @@ jobs:
fi
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "1.27.2"
go-version: "1.27.3"
cache-dependency-path: |
go.sum
module/tools/go.sum
Expand Down Expand Up @@ -344,7 +344,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "1.27.2"
go-version: "1.27.3"
cache-dependency-path: module/services/accounts/code/go.sum
# The suite needs a native PostgreSQL it can initdb and own, not a service
# container: it provisions its own logins, roles and grants, and widens one
Expand Down Expand Up @@ -392,7 +392,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "1.27.2"
go-version: "1.27.3"
cache-dependency-path: |
go.sum
module/tools/go.sum
Expand Down Expand Up @@ -468,7 +468,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "1.27.2"
go-version: "1.27.3"
cache-dependency-path: module/tools/go.sum
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
Expand Down Expand Up @@ -591,7 +591,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "1.27.2"
go-version: "1.27.3"
- name: Reject ineffective image proposals before service CI
env:
CODEFLY_BASE: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha }}
Expand Down Expand Up @@ -723,7 +723,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "1.27.2"
go-version: "1.27.3"
cache-dependency-path: |
go.sum
module/tools/go.sum
Expand Down Expand Up @@ -948,7 +948,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "1.27.2"
go-version: "1.27.3"
cache-dependency-path: |
go.sum
module/tools/go.sum
Expand Down Expand Up @@ -1028,7 +1028,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "1.27.2"
go-version: "1.27.3"
cache-dependency-path: |
go.sum
module/tools/go.sum
Expand Down Expand Up @@ -1181,7 +1181,7 @@ jobs:
fetch-depth: 0
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version: "1.27.2"
go-version: "1.27.3"
cache-dependency-path: module/tools/go.sum
# The release record runs on the same Node every gate is tested against,
# rather than whichever version the runner image happens to ship.
Expand Down
7 changes: 4 additions & 3 deletions module/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,10 @@ Their `go.mod` files are the exact source pins. The other services and tools
retain their own released pins. These are development candidates, not release
qualification: never ship a host release on an untagged pseudo-version.
Run qualification with `GOWORK=off` so a workspace cannot mask the pins.
Accounts requires Go 1.27.2. All go-grpc services pin released agent 0.1.54,
whose builder uses Go 1.27.2 under `GOTOOLCHAIN=local`; CI uses the same patch.
Go 1.27.0 is affected by [GO-2026-6613](https://pkg.go.dev/vuln/GO-2026-6613).
Accounts requires Go 1.27.3. All go-grpc services pin released agent 0.1.58,
whose builder uses Go 1.27.3 under `GOTOOLCHAIN=local`; CI uses the same patch.
Go 1.27.0 is affected by [GO-2026-6613](https://pkg.go.dev/vuln/GO-2026-6613),
and Go 1.27.2 by [GO-2026-6809](https://pkg.go.dev/vuln/GO-2026-6809).
A source audit under a newer compiler does not qualify the compiler used in the
image: move the owning agent and image contract together, regenerate the recipe,
and check the built executable's toolchain evidence. Never lower the accounts
Expand Down
19 changes: 18 additions & 1 deletion module/CALLABLE_SOURCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,24 @@ a connector-owned linker. Authorization-code envelopes hold only provider
tokens and expiry; refresh resolves the client ID, secret, token URL and scopes
from the current deployment registration, so app rotation does not require
copying a new secret into every source. This generic link identifies a source authorization, not a provider
subject or email; it is not a provider-directory ACL mapping.
subject or email; it is not a provider-directory ACL mapping. Its account link
is recorded under connector `api` and provider account `source:<source id>`,
which is how a client finds the link for one source. Deleting that link record
does not remove the authorization stored on the source.

## Connecting in the UI

The host's kit (`@codefly-dev/saas-ui`) connects a person to one API with
`ApiSourceCard`: given a base URL and, optionally, the operation declarations
to declare, it creates the person's `api` source with
`API_CREDENTIAL_KIND_OAUTH2` + `GRANT_AUTHORIZATION_CODE` when they have none,
declares the operations, begins the link with that `source_id`, and completes
it on the page the provider returns to through `DatasourceAccountLinks`
(`connector="api"`, `sourceId`). Where the API and its declarations come from is
the caller's decision; the card takes them as props. `AddSource` and
`DeclareSourceOperations` require an organization administrator today, so a
member without that role is refused and told so. The kit's README describes the
states and the refusals it words.

Refresh and client-credentials resolution reuse the credential row lock.
Transport uses the resolved-IP egress guard and same-origin redirect policy.
Expand Down
7 changes: 7 additions & 0 deletions module/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ see `JOBS.md` for the exact boundary and sequencing.
| Webhooks (system view) | ✅ | List + delete from admin |
| Time-bound impersonation | ✅ | Access tokens are capped at five minutes by default |
| Audit log viewer | ✅ | Same UI as user-facing log, super-admin sees all orgs |
| Data sources | ✅ | Connect a repository, edit what a connected source fetches in place (branch, paths, file types — `UpdateSource`, #1181), reconcile, reconnect, remove; collection read grants beside it |

### Compliance & legal

Expand Down Expand Up @@ -615,6 +616,12 @@ the pre-existing opt-in rule, which refuses an ENROLLED member without recent
evidence. So an enrolled org admin who has not stepped up recently is now refused on
`AssignRole`, `RevokeRole`, `GrantScope`, `RevokeScope`, the role CRUD and
`CreateAgentPrincipal`, where before this change nothing asked. That is intended —
with one exemption: a `GrantScope` of a role whose every permission is a `read`
(a collection shared with a member or a team) asks for no factor, because the
subject gains nothing the factor protects; what is exempted is what the store says
the role grants, never its name, and an unknown role is gated like any other
(`roleGrantsOnlyReads`). When the gate does refuse, the frontend answers with a
step-up (`step_up=1&next=<here>` on the sign-in page), not a bare error —
granting authority inside a tenant is not an ordinary tenant mutation, whoever does
it — and it is stated here because an operator reading only the paragraph above would
not predict the refusal. An admin who has enrolled nothing is still admitted, so no
Expand Down
88 changes: 88 additions & 0 deletions module/WORK_CONTEXTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,88 @@ checked again on the return exchange.
{"example":{"tenant":"019f6bf7-5b4b-74e5-8c17-092259bb1661","operation_audiences":{"generate":{"audience":"example-producer","invoke_scopes":[{"resource_kind":"results","actions":["read","write"]}],"lookup_scopes":[{"resource_kind":"results","actions":["read"]}]}}}}
```

**Installed content reads.**

An operation binding may explicitly set `"delegation_mode": "content_read"`
in the host-owned module envelope grant. Empty or absent mode keeps the ordinary
Model, proof and source-delegation exchange rules above. Unknown modes are
invalid. This mode is retained while the host intersects the envelope ceiling
with the installed manifest; it is never inferred from a resource name or from
whether the target is currently present.

```json
{"audience":"content","delegation_mode":"content_read","invoke_scopes":[{"resource_kind":"documents","actions":["read"]}],"lookup_scopes":[{"resource_kind":"documents","actions":["read"]}]}
```

A marked binding requires nonempty read-only invoke scopes, no headless scopes
and no source-delegation scopes. Optional lookup scopes remain a nonempty
read-only subset of invoke scopes; lookup uses the same restricted child path.
The parent must be a current human-owned root with no actor hops, addressed to
the authenticated caller module. Existing actor parents, source-delegated
parents and delegation references are refused. The host uses Core's Child
operation to attach the actual module service actor and attenuate the parent.
The child seals the exact installed binding plus the origin and target's
current installation, presence generation, producer epoch and envelope revision.
It cannot be renewed, exchanged again or transformed into another session.

A collection-only reader may carry an eligible foreign content-read scope at
StartTask for this installed hop. That is standing to request a read, not a
content grant. Actual source listing and record reads still resolve the human's
current collection/record visibility. The module actor's own permissions are
not added to personal visibility. Removing a collection grant withholds those
sources; removing/replacing either installation, changing presence, or removing
and restoring the binding invalidates an already issued child. Missing or
unavailable owner state cannot fall back to an ordinary exchange.

Changing the mode of an existing binding is a policy restriction, including
adding it where the mode was previously absent. The existing witnessed policy
log path must authorize that envelope transition; adding a JSON field is not
an additive grant. An entirely new binding remains subject to the normal grant
and installation checks. A host without the configured policy-log witness
refuses a mode migration and leaves the stored envelope revision unchanged.
No local substitute witness or automatically inferred migration is supported.

**Installed module actors.**

`"delegation_mode": "module_actor"` is the same installed child for a module
that acts on a person's behalf, so the callee can see which module acted. It
takes the content-read rules above except that its invoke scopes are not
limited to `read`. Its audience is one of three:

- a module, which must serve every kind the binding names;
- a `solution:` audience, held by the organization's installation of that
solution;
- the host's own source operations (`saas-datasource`), where every scope names
`datasource.sources` with explicit source ids and carries only `invoke` and
`read`. The source surface then takes the call as the person's, with the
module acting, for those sources only, and rechecks the person's access to
the source on every call.

Lookup, when declared, still yields only lookup scopes.

```json
{"audience":"solution:example-tools","delegation_mode":"module_actor","invoke_scopes":[{"resource_kind":"tickets","actions":["read","write"]}],"lookup_scopes":[{"resource_kind":"tickets","actions":["read"]}]}
```

The parent must be a current human-owned root with no actor hops, addressed to
the authenticated caller module. Core's Child operation derives owner, tenant,
task and expiry from that parent; the one hop it adds is the authenticated
caller's module principal, never one the request names, so it equals the owner
of the module Work Context the host issued that module. The hop is journaled
like any on-behalf-of hop. The binding id is namespaced `installed-actor/` and
commits to the caller's installation, presence and envelope facts and to the
audience's installation where it has one: a module audience's presence and
installation, or the organization's installation of a solution audience.
Uninstalling, reinstalling or narrowing either side retires every child
already issued. A callee's
revision check confirms the hop from the binding it names
(`ConfirmModuleHopBinding`); only the owner is a question for the
authorization store. The child is terminal, as above.

The mode is the binding's, never the request's. The chain-preserving exchange,
StartTask and registered-agent actors are unchanged, and a binding without a
mode keeps its owner-only child.

Resource kinds, actions and resource ids are sorted and unique; wildcards are
refused. Lookup scopes are optional: a binding that declares only invoke scopes
redeems no lookup, and a lookup exchange under it is refused by name. When present,
Expand Down Expand Up @@ -573,6 +655,12 @@ previous row (`replaced`) and records a new one under the reconnecting person,
atomically. Sources connected before a module declared a binding carry no
delegation; nothing backfills one, so a person must reconnect them.

`UpdateSource` is not one of those acts and records no delegation. It changes
what a source fetches, never its credential, so the authority its syncs already
run under is exactly the authority they keep — and recording a fresh row for the
editor would move a source's sync authority onto whoever last narrowed a path
filter.

**A connect that can record nothing is REFUSED, not reported as done.**
Recording no delegation is correct for a deployment where no composed module
accepts one, and that is the whole answer there. It is a lie when the module
Expand Down
2 changes: 1 addition & 1 deletion module/configurations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ the three services below, all of which need the origin.
| `APP_BASE_URL` | accounts (links it mints, the OAuth issuer, the origin it compares a forwarded one with), auth-gateway (a solution's MCP protected-resource metadata and token audience), frontend (the verified public origin it presents to accounts and judges the solution proxy against) | local only: the frontend uses its loopback own endpoint and accounts trusts the forwarded origin. Outside local, accounts refuses to start and a deployed frontend refuses sign-in, naming the key |
| `BOOTSTRAP_ADMIN_EMAIL` | accounts | no one-time bootstrap administrator |
| `ORGANIZATION_CREATION` | accounts | `open`; `platform_admin` or `disabled` restrict it, anything else refuses the boot |
| `MODULE_AUTHORITY_ENFORCEMENT` | accounts | `enforce`; `warn` grants every module what its manifest asks for and logs, on every registry pass, what the envelope would have withheld (a cell under validation, never a production default — the token, workload, build, installation and tenant stay enforced); anything else refuses the boot |
| `MODULE_AUTHORITY_ENFORCEMENT` | accounts | `enforce`; `warn` grants every module everything its envelope entry allows, widened to what its manifest asks for, and logs, on every registry pass, what the envelope would have withheld (a cell under validation, never a production default — the token, workload, build, installation and tenant stay enforced); anything else refuses the boot |

A deployed frontend never stands its own endpoint in for `APP_BASE_URL` when
that endpoint is loopback, cluster-internal (`*.svc`, `*.cluster.local`, a
Expand Down
4 changes: 4 additions & 0 deletions module/contracts/accounts/saas.events.codefly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ publishes:
schema: saas/events/v1/events.proto#EventEnvelope
visibility: external
retention: 30d
- type: saas.datasource.source.updated
schema: saas/events/v1/events.proto#EventEnvelope
visibility: external
retention: 30d
- type: saas.datasource.sync.completed
schema: saas/events/v1/events.proto#EventEnvelope
visibility: external
Expand Down
Binary file modified module/contracts/api/accounts/authority/contract.binpb
Binary file not shown.
Loading
Loading