Skip to content

feat(tools): make query_lfx_semantic_layer staff-aware with optional project scoping - #105

Merged
emsearcy merged 4 commits into
mainfrom
feat/staff-aware-semantic-layer-tool
Jul 27, 2026
Merged

feat(tools): make query_lfx_semantic_layer staff-aware with optional project scoping#105
emsearcy merged 4 commits into
mainfrom
feat/staff-aware-semantic-layer-tool

Conversation

@josep-reyero

@josep-reyero josep-reyero commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Makes the query_lfx_semantic_layer lens tool support global and cross-foundation queries: project_slug and the where project filter are now optional, and empty values are omitted from the lens request body. When project_slug is provided, lfx-lens still validates where-clause project filters against that foundation's subtree, so scoped queries behave exactly as before.

Since the tool is only registered for staff callers (canRead && isStaff gate, unchanged), this is a single code path — per maintainer review, the earlier two-variant (staff/non-staff) implementation and the is_staff request field were collapsed because the non-staff mode was unreachable dead code. The query_lfx_lens SQL-agent tool is unchanged.

Validated with unit tests covering request-body construction, schema optionality, and description content, plus gofmt, go build, go vet, and go test ./... all green.

Depends on linuxfoundation/lfx-lens#26 (makes project_slug optional on /semantic-layer/query) being deployed first.

🤖 Generated with Claude Code

…project scoping

Parameterize RegisterSemanticLayer by isStaff so the lens semantic-layer
tool has two variants sharing one tool name:

- Staff variant: project_slug is optional for all actions and where is
  optional for the query action, enabling global and cross-foundation
  queries. The request body omits project_slug/where entirely when empty
  (the lens API treats absence as "skip project scope validation").
  Descriptions state capabilities without prescribing when to pass the
  slug, plus the tlf membership-vs-activity data caveat.
- Non-staff variant: preserves the current descriptions, schema
  (project_slug required), and hard enforcement byte-for-byte, guarded
  by a verbatim-description test. It is future-proofing for when tool
  registration is widened beyond staff.

The registration gate in main.go stays canRead && isStaff, so today only
staff (and stdio mode, where callerToken == nil implies isStaff) get the
tool — stdio now serves the staff variant. The two variants use distinct
arg struct types because the shared MCP schema cache is keyed by
reflect.Type. query_lfx_lens is unchanged.

Adds internal/tools/lens_test.go covering both handler variants, request
body key presence/absence, describe text variants, description assembly,
and ListTools schema differences across a shared schema cache.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Josep Garcia-Reyero Sais <josepreyero@gmail.com>
Copilot AI review requested due to automatic review settings July 23, 2026 12:37
@josep-reyero josep-reyero added the ai-assisted A task or activity that was supported by AI, such as CoPilot, ChatGPT, or other AI technology. label Jul 23, 2026
@josep-reyero
josep-reyero requested review from dealako and emsearcy July 23, 2026 12:37

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Makes query_lfx_semantic_layer staff-aware while preserving scoped non-staff behavior.

Changes:

  • Adds staff-specific schema, descriptions, and dispatch behavior.
  • Omits empty project scope fields for global staff queries.
  • Adds comprehensive variant and request-body tests.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
internal/tools/lens.go Implements staff-aware semantic-layer variants.
internal/tools/lens_test.go Tests schemas, enforcement, descriptions, and request bodies.
cmd/lfx-mcp-server/main.go Passes staff status during registration.

The query_lfx_semantic_layer handler now includes is_staff (the verified
lf_staff signal plumbed at tool registration) in the JSON body POSTed to
/lfx-lens/semantic-layer/query, so lfx-lens can keep project-scope
enforcement fail-closed unless the capability is explicitly claimed.
Explicit false is sent for non-staff callers; lfx-lens treats an absent
field as false, so old-mcp -> new-lens stays safe. Companion to
lfx-lens PR #26.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Josep Garcia-Reyero Sais <josepreyero@gmail.com>
Copilot AI review requested due to automatic review settings July 23, 2026 13:16
@josep-reyero

Copy link
Copy Markdown
Contributor Author

New commit 44af199 adds the explicit is_staff field to the semantic-layer request body, matching the updated contract in linuxfoundation/lfx-lens#26: the handler sends the verified isStaff value with every /semantic-layer/query request, and lens fails closed (mandatory project scoping) whenever the flag is false or absent. The deploy dependency on lfx-lens#26 (lens first) still holds.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread cmd/lfx-mcp-server/main.go Outdated
}
if enabledTools["query_lfx_semantic_layer"] && canRead && isStaff {
tools.RegisterSemanticLayer(server)
tools.RegisterSemanticLayer(server, isStaff)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to disagree with Copilot -- anybody running this in stdio would need M2M secrets to hit the semantic layer upstream anyhow, so the fact that staff checks are bypassed for stdio is expected.

HOWEVER, I will call out that the idea of making the semantic layer staff-aware via this line is useless, because the semantic layer tool is already 100% excluded to staff via the immediately preceding && isStaff line ... meaning the tool is never called with anything except true.

@emsearcy emsearcy left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The semantic layer tool is ALREADY restricted to only staff, so simply making the project optional is all that is needed -- no "two modes".

Comment thread cmd/lfx-mcp-server/main.go Outdated
}
if enabledTools["query_lfx_semantic_layer"] && canRead && isStaff {
tools.RegisterSemanticLayer(server)
tools.RegisterSemanticLayer(server, isStaff)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to disagree with Copilot -- anybody running this in stdio would need M2M secrets to hit the semantic layer upstream anyhow, so the fact that staff checks are bypassed for stdio is expected.

HOWEVER, I will call out that the idea of making the semantic layer staff-aware via this line is useless, because the semantic layer tool is already 100% excluded to staff via the immediately preceding && isStaff line ... meaning the tool is never called with anything except true.

…d variant

The tool is only registered for staff callers (canRead && isStaff gate), so
the non-staff variant and the is_staff plumbing were dead code paths. Per
maintainer review, keep one tool definition: project_slug and where are
optional, and lfx-lens validates any provided project filters against the
requested foundation's subtree. The is_staff request-body field is dropped
to match the simplified lfx-lens contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Josep Garcia-Reyero Sais <josepreyero@gmail.com>
Copilot AI review requested due to automatic review settings July 24, 2026 10:02
@josep-reyero

Copy link
Copy Markdown
Contributor Author

Simplified per review: collapsed the two-variant implementation to a single code path — commit 16be5dc removes the non-staff variant, the isStaff registration parameter, and the is_staff request-body field (also dropped from the lfx-lens contract in linuxfoundation/lfx-lens#26). The tool now has one definition with optional project_slug/where; scoping enforcement happens in lfx-lens whenever a slug is provided.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Comment thread internal/tools/lens.go
semanticLayerSlotScopeRule = `project_slug is optional. When provided, where-clause project filters are validated against that foundation's subtree. It may be omitted for global or cross-foundation questions. To scope to a project, add a where filter — check the dimensions list for the correct one (e.g. registration_id__project_slug). Some models don't have project_slug — they use project_name instead. In that case, use the full project name from search_projects (e.g. "Cloud Native Computing Foundation (CNCF)").`

// semanticLayerSlotFinalTip: final tip.
semanticLayerSlotFinalTip = `For membership metrics, a Linux Foundation ('tlf') filter only captures direct LF memberships — for global membership aggregates, omit the project filter. Activity metrics are fanned out to foundations, so either a 'tlf' foundation filter or no filter works for global questions.`
Copilot AI review requested due to automatic review settings July 27, 2026 16:23

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

internal/tools/lens.go:209

  • This new membership guidance conflicts with the same tool description's earlier “All membership questions” instruction to use query_lfx_lens instead (and with the Action schema wording at line 235). An agent cannot reliably determine whether global membership aggregates should use this tool or query_lfx_lens; please make the routing guidance consistent, either by narrowing the earlier prohibition or removing membership instructions from this tool.
	semanticLayerSlotFinalTip = `For membership metrics, a Linux Foundation ('tlf') filter only captures direct LF memberships — for global membership aggregates, omit the project filter. Activity metrics are fanned out to foundations, so either a 'tlf' foundation filter or no filter works for global questions.`

@emsearcy
emsearcy merged commit b2585d9 into main Jul 27, 2026
8 checks passed
@emsearcy
emsearcy deleted the feat/staff-aware-semantic-layer-tool branch July 27, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-assisted A task or activity that was supported by AI, such as CoPilot, ChatGPT, or other AI technology.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants