Spec file and markers

Who this is for: anyone writing or changing the one file that tells servo what to build.

The spec file is the entire input you author. It declares the roots of the object graph, any bindings that resolution can’t work out on its own, and — for an injector that serves //servo: routes — the listener groups, middleware and extractors its emitted servers get. Nothing else. It is read as syntax and never executed, which drives almost every rule on this page.

What a spec file is

//go:build servoinject

package main

import (
	"example.com/servobasic/api"
	"example.com/servobasic/mockstore"
	"example.com/servobasic/postgres"
	"example.com/servobasic/relay"
	"example.com/servobasic/store"
	"example.com/servobasic/worker"
	"github.com/okian/servo/v3/servo"
)

func wire() {
	servo.Build(
		servo.Root[*api.Server](),
		servo.Root[*worker.Consumer](),
		servo.Root[*relay.Relay](),
		servo.Bind[store.Store, *postgres.DB](),
		servo.Override[store.Store, *mockstore.Store](),
	)
}

That is examples/basic’s real spec file, and it uses every marker a service without scopes, HTTP or configuration needs. Scoped appears in examples/scoped’s spec, and the HTTP markers — HTTP with its Group/Use/Route options, and Extract — in examples/http’s; Value, Include, ConfigFile and Trace are on this page too.

Note what is not in it: the //go:generate directive. That lives in an untagged servo_generate.go beside it, because go generate honours build constraints and would never reach a directive inside a //go:build servoinject file. See the go:generate directive.

Four facts about it that matter:

The file’s directory decides where the output goes. servo generate writes servo_gen.go into the same directory as the spec file. Put the spec next to the main.go that will call the generated New.

The function name is a convention, nothing more. Servo searches for a call to servo.Build; it never looks for a function called wire. The name exists so a human reading the file knows why it’s there.

wire is never called, by anything. Nothing in your program references it, and the file never reaches your binary — under the servoinject build configuration it is just an ordinary declared function that nothing happens to call.

Only the Build call is read. The rest of the file can contain whatever you like. It has to be valid Go that type-checks under the servoinject tag, but servo extracts nothing from it.

The build tag

//go:build servoinject

This is required. A spec file without it is a hard error:

spec.go:16:2: spec file is missing a `//go:build servoinject` constraint —
as written it would compile into the real binary

The reason is the markers themselves. Build, Root, Bind, Override, Scoped (with its Linger/Max options), HTTP (with Group/Use/Route), Extract, Value, Include, ConfigFile and Trace (with Expected) all panic when executed — they exist to be read, and a marker call that actually runs means generation was skipped or the tag was missing. Rather than silently returning a nil app, they fail loudly. The build tag is what guarantees they never run: servo generate loads your module with -tags=servoinject, so it sees the spec file, and an ordinary go build doesn’t.

The same requirement extends to any file Include pulls markers from, even though that file is not a spec file: it declares marker calls, so it would compile them into your binary.

The generated file carries the mirror-image constraint, so the two files are never in the same build at once. That’s also why generation never trips over its own previous output.

Mirror image means your whole constraint, with servoinject negated — not a fixed !servoinject. A spec gated //go:build servoinject && !prod generates a file gated //go:build !servoinject && !prod. Everything you wrote besides the tag survives untouched, which is what lets one injector have several generated files that exclude each other: you express the exclusion in your own spec files, and servo carries it across. See Build variants.

The check is on meaning, not on text. Servo parses the constraint and asks whether it can only be satisfied when servoinject is set, evaluating every other tag as true. So //go:build servoinject && linux passes (it still requires the tag), while //go:build servoinject || linux does not (it can be satisfied on Linux without the tag). Both //go:build and the legacy // +build syntax are recognised.

servo-vet runs this identical check as an analyzer, so a missing tag surfaces in your editor rather than at go generate time.

Spec discovery

Servo scans the main module only for servo.Build(...) calls. Packages from dependencies are loaded and scanned for candidate providers, but a Build call in one of them is not your injector and is not treated as one.

Several injectors are normal. A monorepo whose cmd/api, cmd/worker and cmd/migrator each wire their own graph has three injectors, and generate, check and doctor process all three in one pass. Which commands do that and which ask you to pick one is in CLI commands.

Two Build calls in the same package is an error, even across two files. That package can only own one generated file, so a second call is genuinely ambiguous rather than a second injector:

servo: multiple servo.Build(...) calls found in the same package example.com/app/cmd/api
(ambiguous — which one owns the generated file?):
  cmd/api/spec.go:16:2
  cmd/api/extra.go:9:2

No Build call anywhere is an error pointing you at the fix: servo: no servo.Build(...) call found — run `servo init` to scaffold a spec file.

Build

func Build(...Marker)

Declares the injector. Every argument must be a Root, Bind, Override, Scoped, HTTP, Extract, Value, Include, ConfigFile or Trace call written inline — with explicit type arguments for the generic ones, and with a function name for Include.

That is a real constraint, not a style preference. Servo reads the type arguments out of the type-checker’s instantiation info for the call it found in the syntax tree. A marker value stored in a variable, returned from a helper, or collected into a slice and spread has no instantiation at the Build call site to read:

// Fine.
servo.Build(
	servo.Root[*api.Server](),
)

// Not fine — nothing here for servo to read at the Build call.
markers := []servo.Marker{servo.Root[*api.Server]()}
servo.Build(markers...)

Include is what the second form was reaching for, and it works because servo reads the named function’s body as syntax rather than taking a value the call site has already erased.

Anything else in the argument list is rejected with its position: servo.Build argument is not a marker call, or servo.Build argument must be a Root/Bind/Override/Scoped call with explicit type arguments.

Marker is an empty struct. It carries no data and exists only to give the markers a return type that makes Build’s argument list type-check.

Root

func Root[T any]() Marker

Declares T as a root of the graph. The graph servo builds is exactly the transitive closure of its roots: everything a root depends on, directly or indirectly, is constructed, and every other candidate in your module is left out of the generated file entirely.

  • Several roots are normal. An HTTP server, a background worker and a relay can be three independent roots that share a logger; the shared node is constructed once.
  • Roots are usually pointer types (*api.Server), because that’s what constructors usually return. T must match the provider’s result type exactly — see identity is by type.
  • A root may be an interface. It resolves the same way an interface parameter does, by structural search or an explicit Bind.
  • A root with no provider is a diagnostic, reported against the Root[]() call site.

servo why <type> answers the inverse question — which root pulled a given node in.

Bind

func Bind[I, C any]() Marker

Declares that concrete type C is the implementation to use wherever interface I is requested.

You need it when a parameter’s interface type has more than one implementation in your module, which is otherwise an ambiguity diagnostic — and the diagnostic prints the exact Bind lines to choose from. You don’t need it when there’s exactly one implementation; that auto-binds.

Two behaviours worth knowing:

An explicit bind always wins, even over an interface that would have resolved unambiguously on its own. It is a deliberate override, not only a tie-breaker. servo explain reports the selected provider’s binding as explicit bind.

A bind switches resolution to an exact-type lookup. Once I is bound to C, servo looks for the one function returning exactly C and never runs a structural search for I. If nothing returns C, that’s a missing-provider diagnostic for C, with no candidate suggestions — because suggesting alternatives to an explicit instruction would be second-guessing it.

Two rules are enforced at declaration time:

Rule Error
The second type argument must be a concrete type servo.Bind's second type argument must be a concrete type, not an interface (…) — Bind/Override name the concrete implementation, they don't chain to another interface
The same interface may be bound only once servo.Bind[…, ...] declared twice — first at <pos>

The second rule exists so a second Bind can’t silently win over the first with no diagnostic at all.

Override

func Override[I, C any]() Marker

Declares a test-only replacement for I. Overrides are ignored when generating production code and applied — with priority over Bind — when generating the test variant.

Declaring both for the same interface is the intended pattern, not a collision:

servo.Bind[store.Store, *postgres.DB](),        // production
servo.Override[store.Store, *mockstore.Store](), // tests

One Override anywhere in the spec is what makes servo generate emit a second file, servo_gen_test.go, containing NewTestApp and TestApp. With no overrides declared, only servo_gen.go is written. The generated test type and why it can’t share App are covered in Generated API.

Same rules as Bind: the second type argument must be concrete, and the same interface may be overridden only once. Override is checked against other Overrides only, which is why pairing it with a Bind for the same interface is fine.

An override applies to the whole graph, not to one consumer. There is no way to give one component the mock and another the real implementation; see Limitations.

Scoped

func Scoped[T, I any](...ScopeOption) Marker

Declares that T is a keyed, refcounted, lifecycle-managed instance rather than a singleton, and that I — an interface in your own package — is what consumers depend on to reach it.

servo.Build(
	servo.Root[*api.Server](),
	servo.Scoped[*chat.Room, chat.Rooms](
		servo.Linger(30*time.Second),
		servo.Max(10_000),
	),
)

T must declare a ScopeKey method; I must be a non-empty interface declaring only Acquire, Stats, or both. Everything about what that means — the extractor’s shape, which nodes end up in the scope, when an instance is torn down — is on its own page: Scoped instances.

Linger and Max

func Linger(time.Duration) ScopeOption
func Max(int) ScopeOption

The only two ScopeOptions. Both are read as syntax like everything else here, so both arguments have to be constant expressions — 30*time.Second and 10_000 are fine, a package variable or a function call is not.

Omitted, they take servo.DefaultLinger (30 seconds) and servo.DefaultMax (10,000). Neither is legal outside a Scoped argument list, and putting one directly in Build says so.

Two Scoped declarations whose ScopeKey methods return the same key type share one registry, and therefore one policy: declaring different Linger or Max values across them is an error, because there is only one of each to set.

HTTP

func HTTP(...HTTPOption) Marker

Declares that this injector serves the module’s //servo: route directives: servo generate emits one HTTP server per served group — router, typed request decoding, response encoding, middleware chain, lifecycle — into the generated file.

servo.Build(
	servo.HTTP(
		servo.Group("telemetry"),
		servo.Use[*mw.Recover](),
		servo.Use[*mw.Auth](servo.Group("telemetry")),
		servo.Use[*mw.Audit](servo.Route("POST /order/{category}/")),
	),
)

Routes come from the directives; listen addresses, optional TLS files and body limits come from a *servo.HTTPConfig node your own provider supplies. At most one HTTP per Build — a second declaration is reported against the first.

Which injector declares it is the whole point of it being explicit: multiple injectors share one module’s handlers, and a worker binary must not sprout a listener because a sibling API binary’s handlers exist. The directive grammar, groups, middleware and binding rules are on their own page: HTTP routes.

Group and Use and Route

func Group(name string) HTTPOption
func Use[T any](...HTTPOption) HTTPOption
func Route(pattern string) HTTPOption

Group("telemetry") inside HTTP(...) declares a named listener group — this injector then serves routes carrying that trailing token, on the listener HTTPConfig.Groups["telemetry"] describes. The name must be a constant string matching [A-Za-z0-9_-]+, and "default" (the implicit group every HTTP() injector serves) needs no declaration.

Use[T](selectors...) attaches middleware: T is a graph node with a Middleware(next http.Handler) http.Handler method. No selector wraps every group; Group("x") selectors wrap one group’s mux; Route("METHOD /pattern") selectors wrap single routes — one Use selects groups or routes, never both. Declaration order is the wrap order, outermost first. Route is only legal inside a Use, and its pattern must exactly match a served route.

Extract

func Extract[T any]() Marker

Declares T — a graph node with an Extract(r *http.Request) (V, error) method — as an extractor: every //servo: handler parameter of type V becomes a per-request value produced from the request, instead of a graph dependency. An extraction error short-circuits through the same status contract as a handler error. One extractor per produced type, and a type with both a provider and an extractor is a generate-time error.

Value

func Value[T any]() Marker

Declares that T is supplied by the caller rather than built by a provider — a parsed flag set, a version string injected at link time, a *sql.DB opened by a test harness, a fixed clock.

Everything else in the graph is resolved by finding the one function that produces it. A value that only exists once the process is already running has no such function, and the workaround servo left open — a package-level var in main, read back by a small provider beside it — is the global-lookup pattern this version exists to remove.

servo.Build(
	servo.Value[config.Flags](),
	servo.Root[*api.Server](),
)

Declaring one changes the generated API additively. The injector keeps New(ctx) and gains a Values struct and a NewWith that takes it. What exactly gets emitted, including the test variant’s TestValues/NewTestAppWith, is in Generated API.

Three rules, each of which follows from the marker being a declaration rather than a hint:

A Value beats any provider that also produces T. Declaring one is how you say “this comes from the caller”, which is only meaningful if it wins. It sits above the explicit-Bind step in selection precedence, and no diagnostic is produced for the provider it displaces — the spec file said so.

A declared value nothing depends on is a diagnostic, not a silently unused struct field:

cmd/api/spec.go:13:3: servo: servo.Value[example.com/app/conf.Flags]() is declared, but nothing in
the graph depends on example.com/app/conf.Flags

  A declared value becomes a field on the generated Values struct, so this
  one would be supplied by every caller and read by nobody.

  Two ways out:
    - take it as a constructor parameter somewhere the roots reach:
      func New(v example.com/app/conf.Flags) *Thing
    - delete the servo.Value declaration

The position is the servo.Value[…]() call site — the declaration servo is complaining about, not the type. The check is against the production graph only: in the test variant an Override may have swapped out the value’s only consumer, and there the value is left off TestValues instead of refused — see the test variant.

Note that this is checked per generated file, so a value used only by a type an Override replaces is reported when the test variant is resolved.

T is matched by type, exactly as a constructor parameter is. The same identity rules apply: T and *T are different keys, and declaring the same T twice is servo.Value[…]() declared twice — first at <pos>.

A supplied value appears everywhere a constructed node does — App.Graph(), servo graph, servo explain, servo why — at level 0, since the app has it before it builds anything, with its binding reported as supplied and its provider as the caller, via NewWith:

$ servo explain --dir cmd/api conf.Flags
example.com/app/conf.Flags
  provider:     the caller, via NewWith (cmd/api/spec.go:13:3)
  binding:      supplied
  lifetime:     supplied — handed to NewWith once, held for the life of the process
  level:        0
  depends on:   none
  depended on:  *example.com/app/postgres.DB
  capabilities: none

That lifetime line is the limitation worth reading twice: a supplied value is one per app, handed in at NewWith and held for the life of the process. It is not a per-call or per-request value.

Include

func Include(func() []Marker) Marker

Splices another function’s marker list into this Build call. It exists for the module with three binaries whose specs are identical below the transport: the shared markers are written once, and adding a fourth Bind stops being a three-file edit nothing checks.

The shared set is an ordinary function in an ordinary package, carrying the same build tag a spec file carries:

//go:build servoinject

// internal/wiring/wiring.go
package wiring

func Shared() []servo.Marker {
	return []servo.Marker{
		servo.Bind[store.Store, *postgres.DB](),
		servo.Scoped[*chat.Room, chat.Rooms](servo.Linger(time.Minute)),
	}
}

Each spec then carries only what actually differs — here, its own transport’s root:

func wire() {
	servo.Build(
		servo.Include(wiring.Shared),
		servo.Root[*api.Server](),
	)
}

examples/tutorial is the worked case: four injectors sharing one internal/wiring/wiring.go — three hand-written transports and the generated one — where each spec used to be fifty-three lines carrying eleven marker calls, ten of them identical in every binary.

The argument names the function; it is never called. It has to be a plain identifier or a package selector resolving to a declared function — not a function literal, not a method value, not a call. Anything else is servo.Include's argument must name a declared func() []servo.Marker — not a literal, a method value, or a call.

The included file must carry the servoinject build tag, for exactly the reason a spec file does: every marker it returns panics if executed, so an untagged file compiles them into your real binary. Both failure modes have their own message — one for a file this build cannot see at all, one for a file it can see that isn’t gated:

cmd/api/spec.go:12:3: servo.Include names example.com/app/wiring.Shared, which is declared in a
file without a `//go:build servoinject` constraint — as written it would compile into the real
binary, where every marker it returns panics

servo-vet flags the calls in that file directly, which is the faster loop; a shared marker set lives away from any spec file, which is where the tag is easiest to forget.

The body must be exactly one return of one slice literal of marker calls. Its contents are read as syntax by the same code that reads Build’s own argument list. Anything servo would have to execute to know the answer — a variable, a conditional, an append — is refused:

wiring/wiring.go:10:1: Shared must be exactly `return []servo.Marker{ ...marker calls... }` —
its body is read as syntax and never run, so anything servo would have to execute to know the
answer is refused

The function may live in another package, which is the whole point: a shared marker set is not part of any one injector. Servo finds the declaration by walking the injector package’s import graph, so the spec file importing it is what makes it reachable.

Includes may nest. An included function may itself Include another. A cycle is a diagnostic naming the path that closed it, not a hang:

wiring/wiring.go:19:3: servo.Include cycle — Shared includes itself, through:
  example.com/app/wiring.Shared
  example.com/app/wiring.More

Local declarations win. Included markers are spliced in where the Include sits, so a declaration written after it in the spec file supersedes an included one for the same thing: a Bind or Override for the same interface, a Trace for the same interface (with its own Expected list), a Scoped for the same type (with its own Linger and Max), a Value for the same type, an Extract for the same extractor, and the one ConfigFile or HTTP a spec may declare. That is deliberate and is the only ordering that makes a shared set worth having: a shared default you cannot deviate from in one binary is a shared default you stop using. Two local declarations of the same thing are still the duplicate they always were — servo.Bind[…, ...] declared twice.

ConfigFile

func ConfigFile(path string) Marker
servo.Build(
	servo.Root[*api.Server](),
	servo.ConfigFile("config.yaml"),
)

Declares the config file this injector’s //servo:config types read alongside the environment. At most one per Build; the path must be a string literal ending in .json, .yaml, .yml or .toml — like every marker argument it is read as syntax, and the extension decides which decoder the generated code carries, so an env-only or JSON app never gains a yaml dependency.

Precedence per setting is default, then file, then environment — the environment always wins. At runtime the path can be overridden with the CONFIG_FILE environment variable (same extension family only); the declared path being absent is fine, an explicitly set one must exist. Declaring a ConfigFile no config in the graph would read is a diagnostic, the same judgement an unused Value gets — and with the same exception for the test variant, where an Override may have removed the only consumer and the file is simply not read.

The full contract — the directive, the tag grammar, the generated loader, and the rule that every injector sharing a config must agree about declaring a file — is on Generated configuration.

Trace

func Trace[I any](...TraceOption) Marker
func Expected(err error) TraceOption
servo.Build(
	servo.Root[*api.Server](),
	servo.Trace[repository.OrderRepository](servo.Expected(domain.ErrNotFound)),
	servo.Trace[cache.OrderCache](servo.Expected(cache.ErrMiss)),
	servo.Trace[broker.EventPublisher](),
)

Declares that every consumer of interface I receives a generated OpenTelemetry wrapper in place of whatever I resolves to — an explicit Bind, a structural match, a supplied Value, or in the test variant an Override. The wrapper is emitted into the injector package: a type implementing I whose every method with a leading context.Context runs inside a span named <pkg>.<I>/<Method>, hands the span’s context to the implementation, and records a non-nil error on the span. Methods without a context are delegated untouched, and the wrapper has no lifecycle of its own. It depends on go.opentelemetry.io/otel/trace.TracerProvider as an ordinary graph node, which a constructor in your module has to produce. The full contract — what can be traced, exactly what is generated, the tracer provider, and every diagnostic — is on its own page: Traced interfaces.

Four rules at the declaration:

I must be a named interface with at least one method, every method of which the injector package can implement. A concrete type has no seam to wrap, so servo.Trace[*postgres.DB]() is refused with the interface to depend on instead; an unexported method from another package cannot be named from the generated file. The OpenTelemetry trace API itself is refused too.

Expected names an error the wrapper leaves off the span — a cache miss, a not-found — and must name an exported package-level variable or constant of an error type (or one declared in the injector’s own package). It is read as syntax, exactly as Include reads a function’s name: the generated check is errors.Is(err, cache.ErrMiss), so a call, a literal or a local has no value servo could read and is refused.

One Trace per interface per spec file, with the same include rule Bind has: a declaration written after an Include supersedes the included one for the same interface, so a shared set can trace an interface and one injector can still give it its own Expected list.

A traced interface nothing depends on is a diagnostic in the production graph, like an unused Value; in the test variant, where an Override may have removed the consumer, the wrapper is simply not emitted.

Expected

func Expected(err error) TraceOption

Only legal inside a Trace argument list; at Build’s top level it says so. The argument names a sentinel, never evaluates it. See Expected errors.

The go:generate directive

// servo_generate.go — untagged, and holding only this
package main

//go:generate go tool servo generate

Scaffolded by servo init into its own file, not into the spec file, and worth keeping in that form. Both halves are deliberate:

Untagged, in a separate file. go generate honours build constraints, so a directive inside the //go:build servoinject spec file is invisible to go generate ./... — which then exits 0, prints nothing, and generates nothing.

go tool servo, not go run <module path>. A module that requires servo requires it for the marker package alone, so the generator’s own dependencies are not in that module’s build list and go run github.com/okian/servo/v3/cmd/servo fails on a missing go.sum entry. go get -tool github.com/okian/servo/v3/cmd/servo, once per module, puts the generator in go.mod — which also pins its version, so every developer and every CI runner generates with the same one. That matters because two servo versions can legitimately produce two different (both correct) files, and servo check compares bytes.

Errors from this stage

Everything the spec parser can reject, in one place:

Message Cause
no servo.Build(...) call found No injector in the main module
multiple servo.Build(...) calls found in the same package Two Build calls in one package
spec file is missing a `//go:build servoinject` constraint Untagged spec file
servo.Build argument is not a marker call Something other than a call in the argument list
servo.Build argument must be a Root/Bind/Override/Scoped call with explicit type arguments A marker without inline type arguments
unrecognized servo marker "X" inside Build(...) A servo function that isn’t a marker
servo.HTTP() declared twice Two HTTP() markers in one Build, both written locally
servo.Group belongs inside servo.HTTP(...) A Group/Use at Build’s top level
servo.Route belongs inside servo.Use(...) A Route anywhere but a Use selector list
"default" is the implicit group Declaring servo.Group("default")
group name "x y" must match [A-Za-z0-9_-]+ A name the directive token grammar can’t spell
servo.Group("x") declared twice Duplicate group declaration in one HTTP
servo.Use[T] selects groups or routes, not both Mixed selector kinds in one Use
servo.Group's argument must be a constant string The spec is read, never run
servo.Extract[T] declared twice Duplicate extractor declaration, both written locally
servo.Root expects exactly one type argument Root with the wrong arity
servo.Bind/Override expects exactly two type arguments Bind/Override with the wrong arity
second type argument must be a concrete type, not an interface Binding an interface to an interface
servo.Bind[…] declared twice Duplicate bind (or duplicate override) for one interface, both written locally
servo.Value expects exactly one type argument Value with the wrong arity
servo.Value[…]() declared twice One Value per type, unless the first came from an Include
servo.Include takes exactly one argument, the name of a func() []servo.Marker Include with the wrong arity
servo.Include's argument must name a declared func() []servo.Marker A literal, a method value, or a call where the function name belongs
servo.Include names …, whose declaration is not in this build The included function is not reachable in this configuration
servo.Include names …, which is declared in a file without a `//go:build servoinject` constraint The included file is untagged
X must be exactly `return []servo.Marker{ ... }` The included function’s body is something servo would have to run
servo.Include cycle — X includes itself, through: Two included sets include each other
servo.Trace expects exactly one type argument Trace with the wrong arity
servo.Trace's type argument must be an interface, not … A concrete type; depend on the interface instead
servo.Trace's type argument must be a named interface An interface literal
servo.Trace[…]: the interface declares no methods Nothing to wrap
servo.Trace[…]: method m is unexported, and the wrapper is generated into package … The injector package cannot implement it
servo.Trace[…]: method M's signature uses …, an unexported type the wrapper … cannot name The injector package cannot spell the signature
servo.Trace[…]: the OpenTelemetry trace API is what every wrapper is built from Tracing trace.TracerProvider itself
servo.Trace[…] declared twice Two local declarations for one interface
servo.X is not a servo.Trace option Anything but Expected inside Trace(...)
servo.Expected belongs inside servo.Trace[I](...) Expected at the top level of Build
servo.Expected's argument must name a package-level error variable or constant A literal, a call, a local, or a field
servo.Expected(X): X is a …, not an error A package-level name that is not an error
servo.Expected(X): X is unexported, and the generated errors.Is check lives in package … An unexported sentinel from another package
servo.Expected(X) declared twice in the same servo.Trace One mention per sentinel
servo.Scoped expects exactly two type arguments Scoped with the wrong arity
servo.Scoped's first type argument must be the concrete scoped type An interface where the scoped type belongs
servo.Scoped's first type argument must be a pointer, not X Acquire has to be able to return a zero alongside an error
servo.Scoped's second type argument must be an interface A concrete type where the accessor interface belongs
servo.Scoped's accessor interface … declares no methods any is satisfied by everything, which makes the accessor unusable
servo.Scoped[T, …] declared twice One scoped type, one declaration — a local one may replace an included one, policy and all
servo.Scoped[…, I] declared twice One accessor interface cannot stand for two scoped types
servo.Scoped's arguments must be servo.Linger(...) or servo.Max(...) calls Something else in the option list
servo.X is not a scope option A servo marker that isn’t Linger or Max
servo.Linger/Max is a scope option, not a Build marker An option at the top level of Build
servo.Linger/Max declared twice in the same servo.Scoped One value each per declaration
servo.Linger/Max's argument must be a constant expression Read as syntax, never executed
servo.Linger(…) must not be negative Use servo.Linger(0) for die-with-the-last-holder
servo.Max(N) must be positive A scope that can hold no instances can never hand one out
servo runtime package … is not imported by any file this build configuration can see No spec file exists yet, or every one of them is gated out of this configuration

Diagnostics from the later stages — missing providers, ambiguity, cycles — are on the Diagnostics page.