Skip to content

v2/app: look up ServiceForPath in a map of service roots - #2633

Open
twindebank wants to merge 1 commit into
encoredev:mainfrom
gradientlabs-ai:service-for-path-map
Open

twindebank wants to merge 1 commit into
encoredev:mainfrom
gradientlabs-ai:service-for-path-map

Conversation

@twindebank

@twindebank twindebank commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Problem

app.Desc.ServiceForPath finds the service a folder belongs to by checking the folder against every service in turn. The parser calls it for packages, endpoints and resources across the app, so its cost grows with packages times services.

Fix

  1. ValidateAndDescribe builds a map from each service's cleaned root folder to the service.
  2. ServiceForPath looks up the folder, then each folder above it, so the cost is the folder's depth instead of the number of services.
  3. Services cannot nest (discoverServices reports that as an error), so the first root found is the service.

Windows keeps the loop, because its paths compare without regard to case. The comment on the sort by FSRoot in discoverServices said it was for binary searches in ServiceForPath, which the code never did, so it now says what the sort is for.

Impact

One lookup with 300 services, on main (go test -bench, five runs each):

Time per lookup Allocations
Before ~60 µs 299
After ~0.11 µs 0

On a large app (a few hundred services), the validate step went from 1.07 s to 0.31 s, with identical generated code.

Tests

TestDesc_ServiceForPath covers a path at a service root, inside it, inside a nested folder, after .., and outside every service. It also covers a folder that only shares a prefix with a root (/app/bc against /app/b/c). BenchmarkDesc_ServiceForPath measures the 300-service lookup.

go test -short -tags=dev_build ./v2/app passes.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

ServiceForPath checked the path against every service in turn, and the
parser calls it for packages, endpoints and resources across the app.
It now looks up the path and each folder above it in a map of service
root folders, built once in ValidateAndDescribe. Services cannot nest,
so the first root found is the answer; in an app that already fails
with a nesting error, the inner service now wins. Windows keeps the
loop, since its paths compare without regard to case.

The comment on the sort by FSRoot named binary searches here that the
code never did; it now says what the sort is for.
Comment thread v2/app/app.go
// Services cannot nest, so the first service root found walking up from path is the one.
dir := filepath.Clean(string(path))
for {
if svc, ok := d.serviceByRoot[dir]; ok {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 Low - ServiceForPath ignores exported service changes on Unix

A consumer constructs a Desc with its documented exported Services field, or changes a Service's exported FSRoot after construction, and then calls ServiceForPath on Unix. The new implementation consults only the private index built once by ValidateAndDescribe, so it misses newly supplied roots or returns a service for a root that the consumer has replaced; callers that use the result to associate metadata or validate resources consequently classify the path incorrectly.

Show fix

Keep ServiceForPath derived from the current exported Services data, or make Desc/Service construction and mutation private and provide an invariant-preserving constructor/update path. If the indexed representation is retained, rebuild or invalidate serviceByRoot whenever Services or FSRoot can change.

More info - Reply on this comment to give feedback or ignore the issue.

@twindebank twindebank Oct 29, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Reply from the Claude Code session that wrote this change, not from Theo.

ValidateAndDescribe is the only place an app.Desc is built, and it builds the map from the same services slice it stores. Nothing sets a service's FSRoot after that, so the map and Services can't drift today. Happy to add a fallback to the old loop when the map is nil if you'd prefer the extra guard.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant