Skip to content

Commit 2834192

Browse files
authored
feat(control-plane): "not enabled" splash for disabled audit logs & LLM requests (+ bank name fix) (#1950)
* feat(control-plane): show "not enabled" splash for disabled audit logs & LLM requests Add a reusable FeatureNotEnabled component (centered icon + title + description) and use it for the Audit Logs and LLM Requests tabs, plus refactor the existing Observations splash to reuse it. Tabs gain an "Off" badge when the feature is disabled. To let the UI detect server-side gating, expose audit_log and llm_trace in the /version features object (sourced from config.audit_log_enabled / config.llm_trace_enabled), wire them through the features context and the control-plane SDK type, and add i18n keys across all 10 locales. * fix(retain): default bank name to bank_id in ensure_bank_exists ensure_bank_exists inserted banks without a name (NULL), unlike the other creation path (get_or_create_bank_profile, which defaults name to bank_id). Since #1940 wired PATCH /config to ensure_bank_exists, a config PATCH on a never-retained bank (and any retain-only bank) produced a NULL name, which then 500'd the deprecated GET /profile endpoint (name is typed as a required str). Default name to bank_id at insert so every creation path is consistent. Extends the #1940 regression test to assert the auto-created bank's profile returns 200 with name == bank_id. * test(api): assert audit_log and llm_trace flags in /version response * chore: regenerate openapi spec and client SDKs for new feature flags
1 parent 1b3925f commit 2834192

23 files changed

Lines changed: 269 additions & 54 deletions

File tree

hindsight-api-slim/hindsight_api/api/http.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2382,6 +2382,8 @@ class FeaturesInfo(BaseModel):
23822382
file_upload_api: bool = Field(description="Whether file upload/conversion API is enabled")
23832383
document_export_api: bool = Field(description="Whether the document export endpoint is enabled")
23842384
document_import_api: bool = Field(description="Whether the document import endpoint is enabled")
2385+
audit_log: bool = Field(description="Whether audit logging is enabled")
2386+
llm_trace: bool = Field(description="Whether per-bank LLM request tracing is enabled")
23852387

23862388

23872389
class VersionResponse(BaseModel):
@@ -3047,6 +3049,8 @@ async def version_endpoint() -> VersionResponse:
30473049
file_upload_api=config.enable_file_upload_api,
30483050
document_export_api=config.enable_document_export_api,
30493051
document_import_api=config.enable_document_import_api,
3052+
audit_log=config.audit_log_enabled,
3053+
llm_trace=config.llm_trace_enabled,
30503054
),
30513055
)
30523056

hindsight-api-slim/hindsight_api/engine/retain/fact_storage.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,12 +147,13 @@ async def ensure_bank_exists(conn, bank_id: str, ops=None) -> None:
147147
internal_id = uuid.uuid4()
148148
inserted = await conn.fetchval(
149149
f"""
150-
INSERT INTO {fq_table("banks")} (bank_id, disposition, mission, internal_id)
151-
VALUES ($1, $2::jsonb, $3, $4)
150+
INSERT INTO {fq_table("banks")} (bank_id, name, disposition, mission, internal_id)
151+
VALUES ($1, $2, $3::jsonb, $4, $5)
152152
ON CONFLICT (bank_id) DO NOTHING
153153
RETURNING bank_id
154154
""",
155155
bank_id,
156+
bank_id, # Default name is the bank_id (matches get_or_create_bank_profile)
156157
json.dumps(DEFAULT_DISPOSITION),
157158
"",
158159
internal_id,

hindsight-api-slim/tests/test_http_api_integration.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,8 @@ async def test_version_endpoint_returns_correct_version(api_client):
11041104
assert isinstance(features["observations"], bool)
11051105
assert isinstance(features["mcp"], bool)
11061106
assert isinstance(features["worker"], bool)
1107+
assert isinstance(features["audit_log"], bool)
1108+
assert isinstance(features["llm_trace"], bool)
11071109

11081110
print(f"Version endpoint returned: api_version={result['api_version']}, features={features}")
11091111

@@ -1344,6 +1346,12 @@ async def test_patch_config_persists_override_for_uncreated_bank(api_client, fie
13441346
assert body["config"][field] is False
13451347
assert body["overrides"].get(field) is False
13461348

1349+
# The auto-created bank must have a name (defaults to bank_id). A NULL name
1350+
# would 500 the profile endpoint, whose response types name as a required str.
1351+
response = await api_client.get(f"/v1/default/banks/{test_bank_id}/profile")
1352+
assert response.status_code == 200, response.text
1353+
assert response.json()["name"] == test_bank_id
1354+
13471355

13481356
@pytest.mark.hs_llm_core
13491357
@pytest.mark.asyncio

hindsight-clients/go/api/openapi.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5418,11 +5418,21 @@ components:
54185418
description: Whether the document import endpoint is enabled
54195419
title: Document Import Api
54205420
type: boolean
5421+
audit_log:
5422+
description: Whether audit logging is enabled
5423+
title: Audit Log
5424+
type: boolean
5425+
llm_trace:
5426+
description: Whether per-bank LLM request tracing is enabled
5427+
title: Llm Trace
5428+
type: boolean
54215429
required:
5430+
- audit_log
54225431
- bank_config_api
54235432
- document_export_api
54245433
- document_import_api
54255434
- file_upload_api
5435+
- llm_trace
54265436
- mcp
54275437
- observations
54285438
- worker

hindsight-clients/go/model_features_info.go

Lines changed: 59 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

hindsight-clients/python/hindsight_client_api/models/features_info.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ class FeaturesInfo(BaseModel):
3333
file_upload_api: StrictBool = Field(description="Whether file upload/conversion API is enabled")
3434
document_export_api: StrictBool = Field(description="Whether the document export endpoint is enabled")
3535
document_import_api: StrictBool = Field(description="Whether the document import endpoint is enabled")
36-
__properties: ClassVar[List[str]] = ["observations", "mcp", "worker", "bank_config_api", "file_upload_api", "document_export_api", "document_import_api"]
36+
audit_log: StrictBool = Field(description="Whether audit logging is enabled")
37+
llm_trace: StrictBool = Field(description="Whether per-bank LLM request tracing is enabled")
38+
__properties: ClassVar[List[str]] = ["observations", "mcp", "worker", "bank_config_api", "file_upload_api", "document_export_api", "document_import_api", "audit_log", "llm_trace"]
3739

3840
model_config = ConfigDict(
3941
populate_by_name=True,
@@ -92,7 +94,9 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
9294
"bank_config_api": obj.get("bank_config_api"),
9395
"file_upload_api": obj.get("file_upload_api"),
9496
"document_export_api": obj.get("document_export_api"),
95-
"document_import_api": obj.get("document_import_api")
97+
"document_import_api": obj.get("document_import_api"),
98+
"audit_log": obj.get("audit_log"),
99+
"llm_trace": obj.get("llm_trace")
96100
})
97101
return _obj
98102

hindsight-clients/typescript/generated/types.gen.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1676,6 +1676,18 @@ export type FeaturesInfo = {
16761676
* Whether the document import endpoint is enabled
16771677
*/
16781678
document_import_api: boolean;
1679+
/**
1680+
* Audit Log
1681+
*
1682+
* Whether audit logging is enabled
1683+
*/
1684+
audit_log: boolean;
1685+
/**
1686+
* Llm Trace
1687+
*
1688+
* Whether per-bank LLM request tracing is enabled
1689+
*/
1690+
llm_trace: boolean;
16791691
};
16801692

16811693
/**

hindsight-control-plane/src/app/[locale]/banks/[bankId]/page.tsx

Lines changed: 63 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { MentalModelsView } from "@/components/mental-models-view";
1919
import { WebhooksView } from "@/components/webhooks-view";
2020
import { AuditLogsView } from "@/components/audit-logs-view";
2121
import { LLMRequestsView } from "@/components/llm-requests-view";
22+
import { FeatureNotEnabled } from "@/components/feature-not-enabled";
2223
import { useFeatures } from "@/lib/features-context";
2324
import { useBank } from "@/lib/bank-context";
2425
import { bankRoute } from "@/lib/bank-url";
@@ -61,6 +62,8 @@ export default function BankPage() {
6162
const bankConfigTab = (searchParams.get("bankConfigTab") || "general") as BankConfigTab;
6263
const observationsEnabled = features?.observations ?? false;
6364
const bankConfigEnabled = features?.bank_config_api ?? false;
65+
const auditLogEnabled = features?.audit_log ?? false;
66+
const llmTraceEnabled = features?.llm_trace ?? false;
6467

6568
// Bank actions state
6669
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
@@ -331,6 +334,11 @@ export default function BankPage() {
331334
}`}
332335
>
333336
{t("auditLogs")}
337+
{!auditLogEnabled && (
338+
<span className="ml-2 text-xs px-1.5 py-0.5 rounded bg-muted text-muted-foreground">
339+
Off
340+
</span>
341+
)}
334342
{bankConfigTab === "audit-logs" && (
335343
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary" />
336344
)}
@@ -344,6 +352,11 @@ export default function BankPage() {
344352
}`}
345353
>
346354
{t("llmRequests")}
355+
{!llmTraceEnabled && (
356+
<span className="ml-2 text-xs px-1.5 py-0.5 rounded bg-muted text-muted-foreground">
357+
Off
358+
</span>
359+
)}
347360
{bankConfigTab === "llm-requests" && (
348361
<div className="absolute bottom-0 left-0 right-0 h-0.5 bg-primary" />
349362
)}
@@ -378,22 +391,46 @@ export default function BankPage() {
378391
<WebhooksView />
379392
</div>
380393
)}
381-
{bankConfigTab === "audit-logs" && (
382-
<div>
383-
<p className="text-sm text-muted-foreground mb-4">
384-
{t("auditLogsDescription")}
385-
</p>
386-
<AuditLogsView />
387-
</div>
388-
)}
389-
{bankConfigTab === "llm-requests" && (
390-
<div>
391-
<p className="text-sm text-muted-foreground mb-4">
392-
{t("llmRequestsDescription")}
393-
</p>
394-
<LLMRequestsView />
395-
</div>
396-
)}
394+
{bankConfigTab === "audit-logs" &&
395+
(auditLogEnabled ? (
396+
<div>
397+
<p className="text-sm text-muted-foreground mb-4">
398+
{t("auditLogsDescription")}
399+
</p>
400+
<AuditLogsView />
401+
</div>
402+
) : (
403+
<FeatureNotEnabled
404+
title={t("auditLogsNotEnabled")}
405+
description={t.rich("auditLogsDisabledMessage", {
406+
envVar: () => (
407+
<code className="px-1 py-0.5 bg-muted rounded text-xs">
408+
HINDSIGHT_API_AUDIT_LOG_ENABLED=true
409+
</code>
410+
),
411+
})}
412+
/>
413+
))}
414+
{bankConfigTab === "llm-requests" &&
415+
(llmTraceEnabled ? (
416+
<div>
417+
<p className="text-sm text-muted-foreground mb-4">
418+
{t("llmRequestsDescription")}
419+
</p>
420+
<LLMRequestsView />
421+
</div>
422+
) : (
423+
<FeatureNotEnabled
424+
title={t("llmRequestsNotEnabled")}
425+
description={t.rich("llmRequestsDisabledMessage", {
426+
envVar: () => (
427+
<code className="px-1 py-0.5 bg-muted rounded text-xs">
428+
HINDSIGHT_API_LLM_TRACE_ENABLED=true
429+
</code>
430+
),
431+
})}
432+
/>
433+
))}
397434
</div>
398435
</div>
399436
)}
@@ -510,37 +547,16 @@ export default function BankPage() {
510547
<DataView key="observations" factType="observation" />
511548
</div>
512549
) : (
513-
<div className="flex flex-col items-center justify-center py-16 text-center">
514-
<div className="text-muted-foreground mb-2">
515-
<svg
516-
xmlns="http://www.w3.org/2000/svg"
517-
width="48"
518-
height="48"
519-
viewBox="0 0 24 24"
520-
fill="none"
521-
stroke="currentColor"
522-
strokeWidth="1.5"
523-
strokeLinecap="round"
524-
strokeLinejoin="round"
525-
>
526-
<path d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2Z" />
527-
<path d="M12 8v4" />
528-
<path d="M12 16h.01" />
529-
</svg>
530-
</div>
531-
<h3 className="text-lg font-semibold text-foreground mb-1">
532-
{t("observationsNotEnabled")}
533-
</h3>
534-
<p className="text-sm text-muted-foreground max-w-md">
535-
{t.rich("observationsDisabledMessage", {
536-
envVar: () => (
537-
<code className="px-1 py-0.5 bg-muted rounded text-xs">
538-
HINDSIGHT_API_ENABLE_OBSERVATIONS=true
539-
</code>
540-
),
541-
})}
542-
</p>
543-
</div>
550+
<FeatureNotEnabled
551+
title={t("observationsNotEnabled")}
552+
description={t.rich("observationsDisabledMessage", {
553+
envVar: () => (
554+
<code className="px-1 py-0.5 bg-muted rounded text-xs">
555+
HINDSIGHT_API_ENABLE_OBSERVATIONS=true
556+
</code>
557+
),
558+
})}
559+
/>
544560
))}
545561
{subTab === "mental-models" && (
546562
<div>

0 commit comments

Comments
 (0)