Skip to content

Commit 7d75d31

Browse files
sahrizviclaude
andauthored
fix(finops): name the workspace engine tool when a FinOps tool has no local connection (#1346)
* fix(finops): name the workspace engine tool when a FinOps tool has no local connection The `finops_*` tools resolve warehouses from the local connection registry only. In a project bound to a workspace whose Snowflake credentials live in the workspace, every one of them failed with a bare `FAILED`, and the workspace's own cost skills steer the model to exactly these tools first — so a cost question began with four failures before the model fell back to raw `ACCOUNT_USAGE` SQL through `datamate_snowflake_execute_database_query`. - New `tools/finops-workspace.ts`: when the session's precedence snapshot serves `sql_execute` for a type the operation supports (reachability-filtered, the same projection the awareness section uses), append the reason and the engine tool to the failure, with the usage tables to query, and stamp `metadata.workspace_fallback` for telemetry - Every `finops_*` failure branch (dispatcher error and thrown error) goes through `withWorkspaceFallback`; the local reason is kept - Export `QUERY_HISTORY_SUPPORTED_TYPES` / `SNOWFLAKE_ONLY_TYPES` so the wrappers name the types their handler really supports - Tests: served/unserved/disabled/analyst shapes, and the three tool paths through the real handlers with an empty registry Closes #1336 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Q51zFUmPg1WwtS5CrGJE6 * fix(finops): operation-specific tables in the workspace note, canonical identity, undetermined marker Bot review on #1346. - The note names the tables the failing operation's own handler reads, per served type (grants live in `GRANTS_TO_ROLES` / `OBJECT_PRIVILEGES` / `table_privileges`, not in the usage views); a pair with no known table names the engine tool alone - The workspace is rendered with `workspaceLabel(name, id)` — quotes in a customer-authored name cannot break the sentence, and the id rides along - Routing disabled for uncertainty (unattributed, binding unreadable, derive failed) marks the failure `precedence: "undetermined"` and says the workspace could not be consulted; deliberate disablement stays a plain local failure - Test restores the prior `ALTIMATE_TELEMETRY_DISABLED` Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Q51zFUmPg1WwtS5CrGJE6 * fix(finops): re-validate the snapshot, say "unknown" like check() does, runnable recipes Codex review of #1346 (gpt-5.6-sol). - The note trusted the cached precedence snapshot; a project re-linked while the call ran could be pointed at the workspace it had left. The helper is async now and re-validates with `snapshotState` before naming a workspace, as `check()` does before a redirect; relinked/unreadable are said and marked `undetermined` - No snapshot at all (a caller that never resolved tools, an evicted entry) was a silent local failure; it is the unknown `check()` reports - BigQuery recipes named bare `INFORMATION_SCHEMA` views, which are only reachable region-qualified (`bq-utils.ts`): they read `region-<location>.INFORMATION_SCHEMA.…` now. The Snowflake unused-resource recipe adds `QUERY_HISTORY`; warehouse advice adds `SHOW WAREHOUSES` - Tests: every one of the eight wrappers through the real handler (unwrapping any one fails it — checked by mutation); an unbound snapshot stays plain; no snapshot is undetermined; a re-linked project gets no recommendation; every BigQuery recipe is region-qualified Not changed: a FinOps call that SUCCEEDS against an unrelated local connection while the project is bound elsewhere says nothing about it (pre-existing auto-pick behaviour), and the other registry-backed tools (`schema_index`, live PII, data_diff, warehouse_test/list) still fail bare — both are follow-ups, not this issue. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Q51zFUmPg1WwtS5CrGJE6 * fix(finops): one snapshot read on the failure path, explained BigQuery placeholder, neutral wording Bot review of 14aa1a1 on #1346. `workspaceFallbacks` returns the re-validation outcome so the note and the undetermined marker come from one read; the BigQuery `<location>` placeholder is explained (the snapshot carries no location, and the engine runs what it is given); a removed binding is "the workspace binding changed", not "re-linked". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Q51zFUmPg1WwtS5CrGJE6 * docs(finops): drop the orphaned doc block above FallbackLookup (bot review) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Q51zFUmPg1WwtS5CrGJE6 * fix(finops): unprefixed BigQuery location examples; one snapshot read for the reason; stricter tests Bot review of 8c0eb30 on #1346. The note said to replace `<location>` with `region-us`, which yields `region-region-us`; the examples are `us`, `eu`, `us-central1`. The disabled reason is carried on `FallbackLookup` from the same read as the snapshot, so the note cannot describe a different snapshot. The wrapper test requires the real handler's FAILED branch (not a wrapper catch), and precedence announcements are stubbed in setup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012Q51zFUmPg1WwtS5CrGJE6 --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent 1ecfb03 commit 7d75d31

10 files changed

Lines changed: 530 additions & 34 deletions

‎packages/opencode/src/altimate/native/finops/query-history.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { bqRegionFor, interpolateBqRegion } from "./bq-utils"
99
import { resolveFinopsWarehouse } from "./warehouse-resolver"
1010
import type { QueryHistoryParams, QueryHistoryResult } from "../types"
1111

12-
const QUERY_HISTORY_SUPPORTED_TYPES = [
12+
export const QUERY_HISTORY_SUPPORTED_TYPES = [
1313
"snowflake",
1414
"postgres",
1515
"postgresql",

‎packages/opencode/src/altimate/native/finops/role-access.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ LIMIT ?
109109
// ---------------------------------------------------------------------------
110110

111111
const GRANTS_SUPPORTED_TYPES = DEFAULT_FINOPS_TYPES
112-
const SNOWFLAKE_ONLY_TYPES = ["snowflake"] as const
112+
export const SNOWFLAKE_ONLY_TYPES = ["snowflake"] as const
113113

114114
function rowsToRecords(result: { columns: string[]; rows: any[][] }): Record<string, unknown>[] {
115115
return result.rows.map((row) => {

‎packages/opencode/src/altimate/tools/finops-analyze-credits.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import z from "zod"
22
import { Tool } from "../../tool/tool"
33
import { Dispatcher } from "../native"
4+
import { DEFAULT_FINOPS_TYPES } from "../native/finops/warehouse-resolver"
5+
import { withWorkspaceFallback } from "./finops-workspace"
46

57
function formatCreditsAnalysis(
68
totalCredits: number,
@@ -93,11 +95,11 @@ export const FinopsAnalyzeCreditsTool = Tool.define("finops_analyze_credits", {
9395

9496
if (!result.success) {
9597
const error = result.error ?? "Unknown error"
96-
return {
98+
return await withWorkspaceFallback(ctx.sessionID, "analyze_credits", DEFAULT_FINOPS_TYPES, {
9799
title: "Credit Analysis: FAILED",
98100
metadata: { success: false, total_credits: 0, error },
99101
output: `Failed to analyze credits: ${error}`,
100-
}
102+
})
101103
}
102104

103105
const totalCredits = Number(result.total_credits ?? 0)
@@ -114,11 +116,11 @@ export const FinopsAnalyzeCreditsTool = Tool.define("finops_analyze_credits", {
114116
}
115117
} catch (e) {
116118
const msg = e instanceof Error ? e.message : String(e)
117-
return {
119+
return await withWorkspaceFallback(ctx.sessionID, "analyze_credits", DEFAULT_FINOPS_TYPES, {
118120
title: "Credit Analysis: ERROR",
119121
metadata: { success: false, total_credits: 0, error: msg },
120122
output: `Failed to analyze credits: ${msg}`,
121-
}
123+
})
122124
}
123125
},
124126
})

‎packages/opencode/src/altimate/tools/finops-expensive-queries.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import z from "zod"
22
import { Tool } from "../../tool/tool"
33
import { Dispatcher } from "../native"
4+
import { DEFAULT_FINOPS_TYPES } from "../native/finops/warehouse-resolver"
5+
import { withWorkspaceFallback } from "./finops-workspace"
46
import { formatBytes, truncateQuery } from "./finops-formatting"
57

68
function formatExpensiveQueries(queries: unknown[]): string {
@@ -58,11 +60,11 @@ export const FinopsExpensiveQueriesTool = Tool.define("finops_expensive_queries"
5860

5961
if (!result.success) {
6062
const error = result.error ?? "Unknown error"
61-
return {
63+
return await withWorkspaceFallback(ctx.sessionID, "expensive_queries", DEFAULT_FINOPS_TYPES, {
6264
title: "Expensive Queries: FAILED",
6365
metadata: { success: false, query_count: 0, error },
6466
output: `Failed to find expensive queries: ${error}`,
65-
}
67+
})
6668
}
6769

6870
return {
@@ -72,11 +74,11 @@ export const FinopsExpensiveQueriesTool = Tool.define("finops_expensive_queries"
7274
}
7375
} catch (e) {
7476
const msg = e instanceof Error ? e.message : String(e)
75-
return {
77+
return await withWorkspaceFallback(ctx.sessionID, "expensive_queries", DEFAULT_FINOPS_TYPES, {
7678
title: "Expensive Queries: ERROR",
7779
metadata: { success: false, query_count: 0, error: msg },
7880
output: `Failed to find expensive queries: ${msg}`,
79-
}
81+
})
8082
}
8183
},
8284
})

‎packages/opencode/src/altimate/tools/finops-query-history.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import z from "zod"
22
import { Tool } from "../../tool/tool"
33
import { Dispatcher } from "../native"
4+
import { QUERY_HISTORY_SUPPORTED_TYPES } from "../native/finops/query-history"
5+
import { withWorkspaceFallback } from "./finops-workspace"
46
import { formatBytes, truncateQuery } from "./finops-formatting"
57

68
function formatQueryHistory(summary: Record<string, unknown>, queries: unknown[]): string {
@@ -82,11 +84,11 @@ export const FinopsQueryHistoryTool = Tool.define("finops_query_history", {
8284

8385
if (!result.success) {
8486
const error = result.error ?? "Unknown error"
85-
return {
87+
return await withWorkspaceFallback(ctx.sessionID, "query_history", QUERY_HISTORY_SUPPORTED_TYPES, {
8688
title: "Query History: FAILED",
8789
metadata: { success: false, query_count: 0, error },
8890
output: `Failed to fetch query history: ${error}`,
89-
}
91+
})
9092
}
9193

9294
const summary = result.summary as Record<string, unknown>
@@ -97,11 +99,11 @@ export const FinopsQueryHistoryTool = Tool.define("finops_query_history", {
9799
}
98100
} catch (e) {
99101
const msg = e instanceof Error ? e.message : String(e)
100-
return {
102+
return await withWorkspaceFallback(ctx.sessionID, "query_history", QUERY_HISTORY_SUPPORTED_TYPES, {
101103
title: "Query History: ERROR",
102104
metadata: { success: false, query_count: 0, error: msg },
103105
output: `Failed to fetch query history: ${msg}`,
104-
}
106+
})
105107
}
106108
},
107109
})

‎packages/opencode/src/altimate/tools/finops-role-access.ts‎

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import z from "zod"
22
import { Tool } from "../../tool/tool"
33
import { Dispatcher } from "../native"
4+
import { DEFAULT_FINOPS_TYPES } from "../native/finops/warehouse-resolver"
5+
import { SNOWFLAKE_ONLY_TYPES } from "../native/finops/role-access"
6+
import { withWorkspaceFallback } from "./finops-workspace"
47

58
function formatGrants(privilegeSummary: unknown, grants: unknown[]): string {
69
const lines: string[] = []
@@ -121,11 +124,11 @@ export const FinopsRoleGrantsTool = Tool.define("finops_role_grants", {
121124
})
122125

123126
if (!result.success) {
124-
return {
127+
return await withWorkspaceFallback(ctx.sessionID, "role_grants", DEFAULT_FINOPS_TYPES, {
125128
title: "Role Grants: FAILED",
126129
metadata: { success: false, grant_count: 0 },
127130
output: `Failed to query grants: ${result.error ?? "Unknown error"}`,
128-
}
131+
})
129132
}
130133

131134
return {
@@ -135,11 +138,11 @@ export const FinopsRoleGrantsTool = Tool.define("finops_role_grants", {
135138
}
136139
} catch (e) {
137140
const msg = e instanceof Error ? e.message : String(e)
138-
return {
141+
return await withWorkspaceFallback(ctx.sessionID, "role_grants", DEFAULT_FINOPS_TYPES, {
139142
title: "Role Grants: ERROR",
140143
metadata: { success: false, grant_count: 0, error: msg },
141144
output: `Failed to query grants: ${msg}`,
142-
}
145+
})
143146
}
144147
},
145148
})
@@ -159,11 +162,11 @@ export const FinopsRoleHierarchyTool = Tool.define("finops_role_hierarchy", {
159162
const result = await Dispatcher.call("finops.role_hierarchy", { warehouse: args.warehouse })
160163

161164
if (!result.success) {
162-
return {
165+
return await withWorkspaceFallback(ctx.sessionID, "role_hierarchy", SNOWFLAKE_ONLY_TYPES, {
163166
title: "Role Hierarchy: FAILED",
164167
metadata: { success: false, role_count: 0 },
165168
output: `Failed to query role hierarchy: ${result.error ?? "Unknown error"}`,
166-
}
169+
})
167170
}
168171

169172
return {
@@ -173,11 +176,11 @@ export const FinopsRoleHierarchyTool = Tool.define("finops_role_hierarchy", {
173176
}
174177
} catch (e) {
175178
const msg = e instanceof Error ? e.message : String(e)
176-
return {
179+
return await withWorkspaceFallback(ctx.sessionID, "role_hierarchy", SNOWFLAKE_ONLY_TYPES, {
177180
title: "Role Hierarchy: ERROR",
178181
metadata: { success: false, role_count: 0, error: msg },
179182
output: `Failed to query role hierarchy: ${msg}`,
180-
}
183+
})
181184
}
182185
},
183186
})
@@ -203,11 +206,11 @@ export const FinopsUserRolesTool = Tool.define("finops_user_roles", {
203206
})
204207

205208
if (!result.success) {
206-
return {
209+
return await withWorkspaceFallback(ctx.sessionID, "user_roles", SNOWFLAKE_ONLY_TYPES, {
207210
title: "User Roles: FAILED",
208211
metadata: { success: false, assignment_count: 0 },
209212
output: `Failed to query user roles: ${result.error ?? "Unknown error"}`,
210-
}
213+
})
211214
}
212215

213216
return {
@@ -217,11 +220,11 @@ export const FinopsUserRolesTool = Tool.define("finops_user_roles", {
217220
}
218221
} catch (e) {
219222
const msg = e instanceof Error ? e.message : String(e)
220-
return {
223+
return await withWorkspaceFallback(ctx.sessionID, "user_roles", SNOWFLAKE_ONLY_TYPES, {
221224
title: "User Roles: ERROR",
222225
metadata: { success: false, assignment_count: 0, error: msg },
223226
output: `Failed to query user roles: ${msg}`,
224-
}
227+
})
225228
}
226229
},
227230
})

‎packages/opencode/src/altimate/tools/finops-unused-resources.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import z from "zod"
22
import { Tool } from "../../tool/tool"
33
import { Dispatcher } from "../native"
4+
import { DEFAULT_FINOPS_TYPES } from "../native/finops/warehouse-resolver"
5+
import { withWorkspaceFallback } from "./finops-workspace"
46

57
function formatUnusedResources(
68
summary: Record<string, unknown>,
@@ -81,11 +83,11 @@ export const FinopsUnusedResourcesTool = Tool.define("finops_unused_resources",
8183

8284
if (!result.success) {
8385
const error = result.error ?? "Unknown error"
84-
return {
86+
return await withWorkspaceFallback(ctx.sessionID, "unused_resources", DEFAULT_FINOPS_TYPES, {
8587
title: "Unused Resources: FAILED",
8688
metadata: { success: false, unused_count: 0, error },
8789
output: `Failed to find unused resources: ${error}`,
88-
}
90+
})
8991
}
9092

9193
const summary = result.summary as Record<string, unknown>
@@ -98,11 +100,11 @@ export const FinopsUnusedResourcesTool = Tool.define("finops_unused_resources",
98100
}
99101
} catch (e) {
100102
const msg = e instanceof Error ? e.message : String(e)
101-
return {
103+
return await withWorkspaceFallback(ctx.sessionID, "unused_resources", DEFAULT_FINOPS_TYPES, {
102104
title: "Unused Resources: ERROR",
103105
metadata: { success: false, unused_count: 0, error: msg },
104106
output: `Failed to find unused resources: ${msg}`,
105-
}
107+
})
106108
}
107109
},
108110
})

‎packages/opencode/src/altimate/tools/finops-warehouse-advice.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import z from "zod"
22
import { Tool } from "../../tool/tool"
33
import { Dispatcher } from "../native"
4+
import { DEFAULT_FINOPS_TYPES } from "../native/finops/warehouse-resolver"
5+
import { withWorkspaceFallback } from "./finops-workspace"
46

57
function formatWarehouseAdvice(
68
recommendations: unknown[],
@@ -102,11 +104,11 @@ export const FinopsWarehouseAdviceTool = Tool.define("finops_warehouse_advice",
102104

103105
if (!result.success) {
104106
const error = result.error ?? "Unknown error"
105-
return {
107+
return await withWorkspaceFallback(ctx.sessionID, "warehouse_advice", DEFAULT_FINOPS_TYPES, {
106108
title: "Warehouse Advice: FAILED",
107109
metadata: { success: false, recommendation_count: 0, error },
108110
output: `Failed to analyze warehouses: ${error}`,
109-
}
111+
})
110112
}
111113

112114
// Defensive null-coalesce in case the handler ever returns a partial
@@ -125,11 +127,11 @@ export const FinopsWarehouseAdviceTool = Tool.define("finops_warehouse_advice",
125127
}
126128
} catch (e) {
127129
const msg = e instanceof Error ? e.message : String(e)
128-
return {
130+
return await withWorkspaceFallback(ctx.sessionID, "warehouse_advice", DEFAULT_FINOPS_TYPES, {
129131
title: "Warehouse Advice: ERROR",
130132
metadata: { success: false, recommendation_count: 0, error: msg },
131133
output: `Failed to analyze warehouses: ${msg}`,
132-
}
134+
})
133135
}
134136
},
135137
})

0 commit comments

Comments
 (0)