- new deploy/k8s/keycloak/keycloak-bootstrap-job.yaml: one-shot Job that waits for Keycloak, authenticates as admin, creates hwlab realm + hwlab-cloud-web OIDC client via REST API
- secret hwlab-cloud-web-client holds the OIDC client_secret used by cloud-api
- kustomization.yaml picks up the new Job and Secret
- access-control.test.ts: cloud api AgentRun trace records real actor.userId from OIDC-upserted user (issue 788 spec)
- access-control.test.ts: cloud api /v1/users/me returns actor with authMethod=api-key for HWLAB_API_KEY Bearer token
- 31 pass / 4 pre-existing fail
- submitAgentRunChatTurn resolves owner user API key via accessController.store.findActiveDefaultApiKeyForUser
- buildAgentRunRunnerJobInput accepts ownerApiKey; when present, removes HWLAB_DEVICE_POD_API_KEY and adds HWLAB_API_KEY as sensitive
- buildAgentRunTransientEnv no longer emits HWLAB_DEVICE_POD_API_KEY
- codex-stdio child env now reads HWLAB_API_KEY (not HWLAB_DEVICE_POD_API_KEY)
- server-code-agent-http codeAgentAuthEnv looks up user default API key; URL env vars still set unconditionally
- tests: code-agent-session-registry Codex child env carries only device-pod API key now checks HWLAB_API_KEY
- tests: server-agent-chat delegates v0.2 turns removes HWLAB_DEVICE_POD_API_KEY assertion
- 58 pass / 4 pre-existing fail
Adds the user API key foundation called out in #788:
- New api_keys table with idx_api_keys_user and idx_api_keys_prefix
- users extended with auth_provider/keycloak_issuer/keycloak_sub/email/last_login_at
- Authorization: Bearer hwl_live_... resolves to the same AuthPrincipal
- /v1/api-keys, /v1/api-keys/default, /v1/api-keys/{id}/regenerate, DELETE /v1/api-keys/{id}
- First-admin and /auth/login bootstrap a Default API key
- Short-test mode keeps display_secret; production化 will switch to hash-only later
- Legacy HWLAB_DEVICE_POD_API_KEY and Bearer <session-token> paths preserved
- 4 new access-control.test.ts cases
Refs pikasTech/HWLAB#788
P0 freezes the concept boundary between HWLAB (business session/workspace) and
AgentRun (tenant/policy/execution):
- HWLAB adapter now always uses DEFAULT_HWLAB_AGENTRUN_PROJECT_ID /
DEFAULT_HWLAB_AGENTRUN_PROVIDER_ID / DEFAULT_HWLAB_AGENTRUN_BACKEND_PROFILE on
AgentRun runPayload, regardless of caller options. Business projectId belongs
in workspaceRef only, not in AgentRun projectId.
- commandPayload.projectId is now always set to the HWLAB-fixed
DEFAULT_HWLAB_AGENTRUN_PROJECT_ID, never undefined and never derived from
caller options.
- client agent send --session-id <SES> --conversation-id <CONV> without
--provider-profile now looks up the explicit session record to inherit
providerProfile / threadId / conversationId. Mismatched --conversation-id
against the session record aborts with session_conversation_mismatch
before dispatching to AgentRun (prevents stale session state from being
used as a new conversation).
Contract tests added (all 4 passing):
- internal/agent/agentrun-dispatch.test.mjs: HWLAB AgentRun assembly fixes
tenantId/projectId/providerId/backendProfile regardless of options; command
payload does not leak business projectId into AgentRun command projectId.
- tools/hwlab-cli/client.test.ts: send --session-id + --conversation-id
inherits explicit session providerProfile; fails with
session_conversation_mismatch when the session points at a different
conversation.
P2 (CLI/trace result segregation) and P3 (state machine convergence) remain
for follow-up. Tracked via #792 comment chain.
Tracked-by: pikasTech/HWLAB#792