Background
PR #311 (v0.42.0, closes #304) added a sandbox_annotation block to config detail --component-id keboola.sandboxes --config-id <ID> that resolves the misleading parameters.id to the real Storage workspace ID. The annotation lives in commands/config.py, not in ConfigService.get_config_detail().
This means:
Reproduction
kbagent serve --ui
# In another terminal:
kbagent http get "/configs/<alias>/keboola.sandboxes/<sandbox-config-id>" | jq 'has(\"sandbox_annotation\")'
# false ← gap
kbagent --json config detail --project <alias> --component-id keboola.sandboxes --config-id <sandbox-config-id> | jq 'has(\"sandbox_annotation\")'
# true ← CLI has it
Why CLI-only in #311
I chose the path of least risk in PR #311: changing ConfigService.get_config_detail() shape would touch every programmatic consumer, so I scoped the annotation to the CLI layer where I could prove the change end-to-end with the time available. The gap was acknowledged in PR #311 comments but not addressed.
Proposed fix
Add an opt-in parameter to the service layer:
# services/config_service.py
def get_config_detail(
self,
*,
include_sandbox_annotation: bool = False, # ← new, default off
...
) -> dict[str, Any]:
result = ... # existing fetch path
if include_sandbox_annotation and component_id == \"keboola.sandboxes\" and config_id is not None:
result[\"sandbox_annotation\"] = self._resolve_sandbox_annotation(...)
return result
Then:
commands/config.py passes include_sandbox_annotation=True (and removes its current ad-hoc enrichment block).
server/routers/configs.py accepts include_sandbox_annotation: bool = False as a query parameter, opt-in for REST callers. The web UI workspace / config detail page can flip it on once it learns to render the annotation block.
Default False = zero regression risk for existing programmatic consumers; new callers opt in.
Refactor sketch
Pull the filtering logic from WorkspaceService.resolve_sandbox_workspace_id() into a module-level pure function in services/workspace_service.py:
def find_storage_workspace_for_sandbox_config(
workspaces: list[dict[str, Any]],
config_id: str,
) -> int | None:
\"\"\"Pure-function workspace[].configurationId → workspace.id lookup.\"\"\"
...
Both WorkspaceService.resolve_sandbox_workspace_id (callers: CLI) AND ConfigService.get_config_detail (callers: CLI + HTTP) call this helper. Avoids the circular ConfigService → WorkspaceService dependency that would otherwise creep into the DI graph.
Tests to add
test_config_service.py::TestSandboxAnnotation -- service emits annotation iff flag is True + component is keboola.sandboxes + single-config mode.
test_config_service.py regression -- existing programmatic consumers (default include_sandbox_annotation=False) get the unchanged shape.
- HTTP endpoint test --
GET /configs/.../keboola.sandboxes/<id>?include_sandbox_annotation=true returns the block; without the flag it does not.
Related
Out of scope for this issue
- Web UI rendering of the annotation block (separate PR; this issue is service+HTTP parity only).
Background
PR #311 (v0.42.0, closes #304) added a
sandbox_annotationblock toconfig detail --component-id keboola.sandboxes --config-id <ID>that resolves the misleadingparameters.idto the real Storage workspace ID. The annotation lives incommands/config.py, not inConfigService.get_config_detail().This means:
kbagent config detail ...) get the annotation.GET /configs/{project}/keboola.sandboxes/{config_id}viakbagent serve) get the raw API response without the annotation -- they hit the sameparameters.idtrap the original issue described./configspage) and any scheduled agent that calls the REST endpoint stay broken in the same way Discoverability gap: finding a Query-Service-compatible workspace for data-app local dev is tedious and dead-ends silently #304 described.Reproduction
Why CLI-only in #311
I chose the path of least risk in PR #311: changing
ConfigService.get_config_detail()shape would touch every programmatic consumer, so I scoped the annotation to the CLI layer where I could prove the change end-to-end with the time available. The gap was acknowledged in PR #311 comments but not addressed.Proposed fix
Add an opt-in parameter to the service layer:
Then:
commands/config.pypassesinclude_sandbox_annotation=True(and removes its current ad-hoc enrichment block).server/routers/configs.pyacceptsinclude_sandbox_annotation: bool = Falseas a query parameter, opt-in for REST callers. The web UI workspace / config detail page can flip it on once it learns to render the annotation block.Default
False= zero regression risk for existing programmatic consumers; new callers opt in.Refactor sketch
Pull the filtering logic from
WorkspaceService.resolve_sandbox_workspace_id()into a module-level pure function inservices/workspace_service.py:Both
WorkspaceService.resolve_sandbox_workspace_id(callers: CLI) ANDConfigService.get_config_detail(callers: CLI + HTTP) call this helper. Avoids the circularConfigService → WorkspaceServicedependency that would otherwise creep into the DI graph.Tests to add
test_config_service.py::TestSandboxAnnotation-- service emits annotation iff flag is True + component is keboola.sandboxes + single-config mode.test_config_service.pyregression -- existing programmatic consumers (defaultinclude_sandbox_annotation=False) get the unchanged shape.GET /configs/.../keboola.sandboxes/<id>?include_sandbox_annotation=truereturns the block; without the flag it does not.Related
Out of scope for this issue