- 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
Tracks pikasTech/HWLAB#773. PR #765 fixed selector confusion but did
not touch cloud-api evidence propagation. This change closes the
real root cause and adds the read-only evidence selectors that #760
follow-up called for.
cloud-api (internal/cloud/access-control.ts):
- DEVICE_JOB_INTENTS adds workspace.evidence and debug.evidence.
- DEVICE_JOB_READ_ONLY_SUB_ACTIONS = { status, output, wait, cancel,
evidence } and DEVICE_JOB_ACTIONABLE_INTENTS = { workspace.build,
debug.download } make the mutating-intent / sub-action matrix
explicit.
- _deviceJobRequiresReason(intent, args, reason) returns false when
the caller already provided reason OR when the actionable mutating
intent is paired with a read-only sub-action. debug.reset and other
non-actionable mutating intents still always require reason.
- executorOutputPayload now also surfaces output.summary,
nestedOutput.summary, evidence.text, evidence.logTail and
evidence.summary as body.output.text, so a dispatcher that
includes the host logTail / buildSummary in its result becomes
visible to the Code Agent without a separate bootsharp dance.
device-pod executor (cmd/hwlab-device-pod/main.ts):
- gatewayDispatchText also walks result.evidence.{text,logTail,
summary}, dispatch.message (only when dispatchStatus=completed),
dispatch.summary, result.summary and dispatch.buildSummary before
falling back to JSON.stringify.
- deviceHostArgs maps workspace.evidence / debug.evidence to the
host device-host-cli evidence subcommands.
device-host-cli (skills/device-pod-cli/assets/device-host-cli.mjs):
- new readJobEvidence(kindPrefix, requestedId, { tail, full,
target }) reads the most recent (or specified) keil-build /
keil-download job log file and returns it as keil-build.evidence /
keil-download.evidence. tail defaults to 200, --full for entire
log. Missing job returns ok=false but does not throw.
- workspace build evidence and debug-probe download evidence now
wired in main() and help text.
device-pod-cli (tools/src/device-pod-cli-lib.ts):
- selectorCheatSheet adds the evidence row and clarifies that
build/download status/output/wait/cancel/evidence are read-only
and do NOT need --reason. build/download start still require
--reason. usage examples now include the two evidence selectors.
- failed-fast selectors are unchanged; existing 26 tests still pass.
SKILL.md (skills/device-pod-cli/SKILL.md):
- Selector Cheat Sheet adds workspace.evidence and
debug.evidence rows.
- #773 follow-up note: --reason is now required only for
mutating sub-actions, not for the read-only ones.
cloud-api tests (internal/cloud/access-control.test.ts):
- workspace.evidence and debug.evidence must be in DEVICE_JOB_INTENTS.
- _deviceJobRequiresReason signature and the read-only / actionable
branch table.
- executorOutputPayload must look at evidence and summary fields
in addition to text. 3 new tests; pre-existing 4 workbench
failures are unrelated to this change and reproduce on the
unchanged v0.2 base.
Verification (host-side, G14 /root/hwlab-v02, source=0cf9a8c6):
- bun test tools/device-pod-cli.test.ts → 26/26 pass.
- bun test internal/cloud/access-control.test.ts → 23 pass,
4 pre-existing workbench failures (unchanged on v0.2 base).
- bun test cmd/hwlab-device-pod/main.test.ts → 7/7 pass.
- node --check skills/device-pod-cli/assets/device-host-cli.mjs
→ syntax OK.
Hot probe: live v0.2 cloud-api at 74.48.78.17:19667 confirmed
job_devicepod_804c5db4... (workspace.build) returned
text="", bytes=0, output={} before this change. After the
executor text-extraction update and the new evidence selector,
a follow-up workspace.build start + build evidence will surface
the host logTail / buildSummary in body.output.text without
requiring bootsharp + host file fallback.
Tracked-by: pikasTech/HWLAB#773