The keelbase-client module gives Java/Spring systems first-class access to two KeelBase capabilities:
- Delegated identity as a client (
KeelbaseClient) — when a user session holds a KeelBase JWT, obtain a short-lived delegation token bound to a targetaudience, cache it with proactive refresh, and verify it locally with the sharedDELEGATION_SECRET. - Audit reporting (
KeelbaseAuditReporter) — report Java-side business actions to the governance audit chain (D2-3a/external/audit), so what happened in your system is visible alongside KeelBase's own AI audit.
Both are auto-configured by the starter; you only add properties and inject the beans.
Your Java system integrates with KeelBase as a client: a user signs into KeelBase (or SSO), your system holds their KeelBase access token, and you need to act on their behalf across system boundaries. KeelBase signs a short-lived delegation JWT (sub = their user id, aud = your target system, default 300s) — see delegated identity for how receivers verify it.
keelbase:
client:
base-url: http://localhost:3000 # KeelBase service root (for /api/v1/auth/delegation-token)
audience: legacy-crm # optional; falls back to keelbase.delegation.audience
# connect-timeout: 3s
# read-timeout: 10s@Autowired KeelbaseClient keelbaseClient;
// 1. Obtain a delegation token bound to the target audience (Bearer = the user's KeelBase JWT)
KeelbaseTokenIssue issue = keelbaseClient.obtain(userJwt, "legacy-crm", 300);
// issue.token() / subject() (oidcSub or local:<userId>) / expiresIn() / userId() / audience()
// 2. Cache + proactive refresh: same (jwt, audience) reuses the cached token until ≤30s remain
String token = keelbaseClient.obtainAndCache(userJwt, "legacy-crm", 300);
// 3. Verify a delegation token you received (shared DELEGATION_SECRET, HS256 + audience)
keelbaseClient.verify(token, "legacy-crm"); // throws KeelbaseClientException on bad sig / wrong aud
// 3b. Verify and get the parsed identity (subject / oidcSub / audience / expiresAt)
Map<String, Object> identity = keelbaseClient.verifyAndGet(token, "legacy-crm");
// identity.get("oidcSub") -> map to your local userContract (matches KeelBase POST /api/v1/auth/delegation-token):
| Item | Value |
|---|---|
| Request | { audience: string, ttlSeconds?: 60-3600 (default 300) }, Authorization: Bearer <user JWT> |
Response (unwrapped data) |
{ token, subject, expiresIn, userId, audience } |
| Verified locally | HS256 with DELEGATION_SECRET + aud + expiry |
keelbase.client.base-url unset → only verify is available; obtain throws a clear configuration error.
Points at the governance control plane (D2-3a). Unset base-url → reporter is disabled and only logs locally (same semantics as KeelBase's GovernanceReporter).
keelbase:
audit:
base-url: http://localhost:3001 # governance control plane root
api-key: ${GOVERNANCE_API_KEY:} # service identity (x-api-key header)@Autowired KeelbaseAuditReporter audit;
audit.report(KeelbaseAuditEvent.builder()
.userId("42")
.username("alex")
.action("compensation.followups.revoke")
.detail("revoked followup id=7")
.build()); // source defaults to "java"; async, non-blocking, silent on failureThe event is sent as POST {base-url}/api/v1/external/audit with x-api-key, landing in the governance audit chain with source: "java". See the example app's FollowupController.revoke, which reports after a real compensation.
Your business system can enforce the governance plane's real-time policy locally (tool toggles / confirmation / role allow-lists / audit granularity). Config reuses keelbase.audit.base-url + api-key (governance-plane service identity):
@Autowired KeelbasePolicyClient policy;
Optional<GovernancePolicy> opt = policy.fetch();
opt.ifPresent(p -> {
// override (null = not overridden; fall back to your local default)
Boolean confirmation = p.tools().get("create_followup") == null
? null : p.tools().get("create_followup").requiresConfirmation();
String granularity = p.auditGranularity(); // "all" | "write" | "off"
});Endpoint: GET /api/v1/external/governance/policy on the governance plane with x-api-key (same service identity as audit reporting). tools carries only overridden fields (partial override) — merge with your own tool defaults to get effective values. With base-url/api-key unset → Optional.empty() (all-local defaults); HTTP/parse failure throws KeelbaseClientException.
Java-side reconciliation: is a given AI-initiated business action (e.g. followup/7) recorded as a side effect, and has it been revoked? Service identity hits the main app's GET /api/v1/external/effects/:resultType/:resultId:
// config: keelbase.client.base-url (KeelBase main app) + side-effect-api-key (= its GOVERNANCE_API_KEY)
@Autowired KeelbaseClient client;
SideEffectStatus s = client.querySideEffect("followup", 7);
if (!s.found()) {
// no AI side effect for that action (not AI-created / gone) — treat as ordinary data
} else if (s.revoked()) {
// revoked (local entity = target soft-deleted)
} else if (s.revokeHint() != null) {
// B-path proxy_call: revocation goes through the Java compensation endpoint —
// confirm the revoked state on the Java side (honest boundary)
}Config (keelbase.client.*):
keelbase:
client:
base-url: http://localhost:3000 # KeelBase main app (same base as delegation token)
side-effect-api-key: ${GOVERNANCE_API_KEY} # x-api-key service identity the main app acceptsSemantics: local entities (event/todo/crm_task, etc.) — revoked = targetSoftDeleted is the truth; B-path proxy_call has no revoke column on the main-app effect row (revocation goes through the Java compensation endpoint) → revokeHint says "confirm on the Java side" rather than overclaiming. HTTP 404 → SideEffectStatus.notFound() (found=false); missing side-effect-api-key / ≥300 → throws KeelbaseClientException.
- delegated identity — how receivers verify delegation JWTs (
DelegationAuthFilter) - troubleshooting —
KeelbaseClientExceptioncauses