Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
67af0b5
chore: project deixic-python from Mono b31318e06ac4
github-actions[bot] Sep 20, 2026
9d81948
chore: project deixic-python from Mono 9ffaa22bee2d
github-actions[bot] Sep 20, 2026
8a9179a
chore: project deixic-python from Mono 67f00d037a06
github-actions[bot] Sep 20, 2026
af42916
chore: project deixic-python from Mono 0274e34d132f
github-actions[bot] Sep 20, 2026
aa632f5
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 20, 2026
39f9ff2
chore: project deixic-python from Mono 8420a1f8c5fd
github-actions[bot] Sep 20, 2026
3a35c53
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 20, 2026
63c9311
chore: project deixic-python from Mono a2e8b231b208
Sep 20, 2026
bc4469a
chore: project deixic-python from Mono cc1e1c0a4251
Sep 20, 2026
ae5688d
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 21, 2026
bf49e72
chore: project deixic-python from Mono 9b559bc70e7d
Sep 21, 2026
bc436f1
chore: project deixic-python from Mono 78799bd5ae75
Sep 21, 2026
f71d41b
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 21, 2026
efb9463
chore: project deixic-python from Mono 9a003786d767
Sep 21, 2026
c6fab6c
chore: project deixic-python from Mono 511bd2305f5a
Sep 21, 2026
33766ff
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 21, 2026
8d1cb65
chore: project deixic-python from Mono ad7b9df0b712
Sep 21, 2026
c51a704
chore: project deixic-python from Mono 3a95acea6719
Sep 21, 2026
c9111df
chore: project deixic-python from Mono c16f3515c56a
Sep 21, 2026
cc235a2
chore: project deixic-python from Mono 4ab4845398fd
Sep 21, 2026
d79a858
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 22, 2026
0325b9f
chore: project deixic-python from Mono 43ad52f7bcc9
Sep 22, 2026
5df6234
chore: project deixic-python from Mono 2df44a8283c0
Sep 22, 2026
702c1fc
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 22, 2026
ac04f04
chore: project deixic-python from Mono f6e0aa99e756
Sep 22, 2026
084be5b
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 22, 2026
2b700b4
chore: project deixic-python from Mono 3457dcec675e
Sep 22, 2026
729b13d
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 22, 2026
84aa796
chore: project deixic-python from Mono 2102d45bdf4c
Sep 22, 2026
3534ced
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 23, 2026
4d7e85b
chore: project deixic-python from Mono 0135ea0655c8
Sep 23, 2026
0e89e4e
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 23, 2026
f5bc48b
chore: project deixic-python from Mono 7f322bbd03a5
Sep 23, 2026
22fd8c1
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 23, 2026
f9e5c98
chore: project deixic-python from Mono c3fe404d7c05
Sep 23, 2026
fa65638
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 23, 2026
4514f87
chore: project deixic-python from Mono 53a87ef3a96f
Sep 23, 2026
6216d13
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 24, 2026
b0692b8
chore: project deixic-python from Mono 0405bd95fc12
Sep 24, 2026
6f0ab97
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 24, 2026
baa1e61
chore: project deixic-python from Mono 916d407917e8
Sep 24, 2026
d14a597
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 24, 2026
d1bd3d8
chore: project deixic-python from Mono c27319825e1c
Sep 24, 2026
4b105bd
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 24, 2026
c1cdcad
chore: project deixic-python from Mono 274db22a3ad9
Sep 24, 2026
2ad811e
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 24, 2026
ecdbcfd
chore: project deixic-python from Mono a971433d12b4
Sep 24, 2026
4e1d557
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 24, 2026
5bb192d
chore: project deixic-python from Mono c97cbb033be2
Sep 24, 2026
d869810
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 24, 2026
fe5a3ce
chore: project deixic-python from Mono ddaee8a19ef0
Sep 24, 2026
9cac988
Merge remote-tracking branch 'origin/main' into sync/mono-projection
github-actions[bot] Sep 25, 2026
9f2959d
chore: project deixic-python from Mono 80416a066acc
Sep 25, 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
10 changes: 5 additions & 5 deletions .repository-projection.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"projection": "deixic-python",
"projectionSchemaVersion": 1,
"sourceRepository": "dx-corp/mono",
"sourceSha": "ddaee8a19ef06ccd953aa67505182ec41e78f13c",
"sourceSha": "80416a066acccdb2394733a9db4adcae19d88f23",
"destinationRepository": "dx-corp/deixic-python",
"priorProjectedBase": "334735fa42a46ba1f3bd05f768a31d3bcbcda394",
"definitionDigest": "01efa736c67353b6c6e353d7e2ab6886ecd21b68f31fe1a89234403247abcbdb",
"toolDigest": "0aae6000dbd0940f5a0af380463ccb5a83285eda",
"contentDigest": "14e4cd30768fa913750e0b17c4bca966269f33b72c226ac8924df966c045fbcf",
"priorProjectedBase": "be2ecd40236afbb15ee257b69e86ec6832226803",
"definitionDigest": "eedaf99642ef977b56b6e12ce151e1834670560f00294e89224f0628882937d9",
"toolDigest": "cef8579e533dbfa40cbd9070dd7581f65f800e5f",
"contentDigest": "13e942ba6333d3a42f22f7aeb9a324042fc4e8a56c52eaa28c67814b696f8a3b",
"publicationEligible": true
}
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

- Add `WorkloadFederationCredentialProvider` with GitHub Actions, file, and
environment-variable assertion sources for keyless workload authentication.
- Report missing or removed model routes during setup instead of declaring the
workspace accessible, and preserve plain-text account briefs when account
names contain structured-output instruction text. Keep rejected credential
Expand Down
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,77 @@ provide a `CredentialProvider`; an authentication replay is allowed only when
the refreshed credential retains the same subject, tenant, and declared
scopes.

## Workload federation

A CI job or cloud workload can authenticate without a stored API key. An
organization admin first registers an issuer, a service account, and a rule in
Identity settings, as described in the
[workload federation guide](https://github.com/dx-corp/mono/blob/main/docs/services/identity/workload-federation.md).
The workload then exchanges a signed assertion from its platform for a Deixic
access token that lasts at most 300 seconds:

```python
import os

from deixic import (
Deixic,
WorkloadFederationCredentialProvider,
github_actions_assertion_source,
)

identity_url = os.environ["DEIXIC_IDENTITY_URL"]
credentials = WorkloadFederationCredentialProvider(
identity_url=identity_url,
assertion_source=github_actions_assertion_source(
audience=f"{identity_url}/v1/workload-federation/exchange",
),
)
deixic = Deixic(
credential_provider=credentials,
organization_id="org_123",
workspace_id="ws_456",
)
```

The provider exchanges an assertion on the first request and caches the token.
It exchanges again 60 seconds before expiry (`refresh_margin`) and after an
HTTP 401 from Deixic. Identity accepts each assertion once, so every exchange
calls the assertion source for a new assertion. The provider refuses to send
an assertion it has already exchanged and raises `DeixicError` with code
`workload_assertion_reused`. When an early refresh cannot obtain a new
assertion and the cached token has not expired, the provider keeps using the
cached token.

Assertion sources:

- `github_actions_assertion_source(audience)` requests a new OIDC token from
GitHub Actions on each call. The job needs `permissions: id-token: write`.
- `file_assertion_source(path)` reads a file on each call, such as a Kubernetes
projected service-account token. The kubelet rewrites that file after 80% of
the token's `expirationSeconds`. A file token can therefore be exchanged once
per rotation. Set `expirationSeconds` so that rotation happens more often
than the 300-second Deixic token lifetime, or pass a callable that requests
a new token from the Kubernetes TokenRequest API.
- `environment_assertion_source(name)` reads an environment variable on each
call. The application must write a new value before each exchange.
- Any zero-argument callable that returns a new JWT string.

Exchange failures raise `DeixicError`:

| HTTP status | `kind` | `code` | Retried |
| --- | --- | --- | --- |
| 400 | `validation` | `workload_assertion_invalid` | No |
| 403 | `authorization` | `workload_federation_forbidden` | No |
| 409 | `conflict` | `workload_assertion_replayed` | No |
| 503 | `unavailable` | `workload_federation_unavailable` | Yes |
| Transport failure | `transport` | `workload_exchange_transport` | Yes |

A retry waits `retry_delay` seconds, doubled on each attempt, up to
`max_attempts` total attempts (default 3). Each retry uses a new assertion.
A 403 means no active rule matched the assertion's issuer, audience, subject,
and claims. The provider never logs the assertion or the access token and
omits both from error messages.

## Coding output readback

`messages.send(coding_acceptance=contract)` sends the typed coding contract and
Expand Down
12 changes: 12 additions & 0 deletions src/deixic/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
from .auth import Credential, CredentialProvider
from .client import Deixic
from .errors import DeixicError
from .federation import (
AssertionSource,
WorkloadFederationCredentialProvider,
environment_assertion_source,
file_assertion_source,
github_actions_assertion_source,
)
from .tasks import SetupCheck, Task, TaskCheckpoint, TaskResult

__all__ = [
"AssertionSource",
"Credential",
"CredentialProvider",
"Deixic",
Expand All @@ -12,4 +20,8 @@
"Task",
"TaskCheckpoint",
"TaskResult",
"WorkloadFederationCredentialProvider",
"environment_assertion_source",
"file_assertion_source",
"github_actions_assertion_source",
]
Loading