Commit abb8d81
authored
feat(remoteagent): apply per-request auth to A2A via Config.Auth (#1150)
* feat(remoteagent): apply per-request auth to A2A via Config.Auth
Wire the auth package into the A2A remote agent. When A2AConfig.Auth is set,
NewA2A registers an a2aclient.AuthInterceptor backed by the provider and scopes
it to the ADK session id (attached before each send), so the credential is
attached to requests whose agent card declares a matching security requirement.
An unexported credentialsService adapts auth.CredentialProvider to
a2aclient.CredentialsService, returning the raw token/key the interceptor places
per scheme. The adapter lives in remoteagent (not auth/) so the core auth package
stays free of the a2a-go dependency. Auth combined with a custom ClientProvider
is a configuration error, since the interceptor cannot be injected into a
caller-built client.
* fix(remoteagent): apply review feedback to A2A auth
Polish the per-request A2A auth wiring in response to review:
- Document that Auth resolution is fail-open: the a2a interceptor swallows
provider errors (logs and continues), so a failed resolution sends the
request unauthenticated rather than failing the call.
- Error on an OAuth2 credential with an empty AccessToken (and no TokenSource)
instead of emitting an empty "Bearer " header, matching auth.Credential.Apply.
- Tests: use t.Context(); cover the empty-OAuth2 error path and the
AgentCardProvider (per-invocation card) path in the header-attach test.
- Minor: separate the Auth struct field with a blank line.
* test(remoteagent): broaden A2A auth coverage
The initial auth tests only proved a bearer token reaches the server on the
streaming path. Add end-to-end coverage for the paths a real caller hits:
- apiKey scheme: the raw key lands in the card-named header (X-Api-Key), not
in Authorization and without a "Bearer " prefix.
- credential is usable, not just present: an enforcing server accepts the
right token and rejects a wrong one (surfaced as an error event).
- per-session scoping: a session-aware provider reading
a2aclient.SessionIDFrom resolves a distinct credential per ADK session.
- non-streaming (SendMessage) path, alongside the existing streaming one.
Factor the repeated server/card setup into serveRecordingA2A, newSecureCard,
and bearerCard helpers to keep the new cases readable.
* fix(remoteagent): authenticate the A2A task cleanup CancelTask
Auth scoped the session id only onto the message send, so the CancelTask the
run loop issues from its deferred cleanup (when Run exits before a terminal
event, leaving a non-terminal task) went out unauthenticated. Against a remote
agent whose card requires auth the cancel is rejected, leaking the remote task —
exactly the secured agents where cleanup matters most.
Compute the session-scoped context once, before the deferred cleanup, and pass
it to cleanupRemoteTask so both the send and the CancelTask (and any custom
RemoteTaskCleanupCallback) carry the resolved credential. Add a regression test
that fails (empty Authorization on CancelTask) without the fix.
* fix(remoteagent): adapt A2A auth to interface-based auth.Credential
After the auth core package (#1143) merged to main, auth.Credential is an
interface (BearerCredential/APIKeyCredential/OAuth2Credential implementing
Apply), not the earlier struct tagged-union. Rewrite credentialValue to
type-switch on the concrete credential types and update the tests. Surfaced
when rebasing this branch onto main.
* fix(remoteagent): validate a2a credential values and wrap resolve error
- Wrap the provider resolution error with %w so attribution survives
when the a2a interceptor logs it.
- Reject an empty API-key value and an empty OAuth2 access token (a
misbehaving source) rather than transmitting an empty secret.
- Note the scheme-unaware consequence in the adapter doc: a card that
declares an unexpected scheme places the secret per that scheme.
- Broaden credentialValue coverage (nil, unsupported kind, empty
api-key/oauth2) and assert the exact Auth+ClientProvider config error.
* test(remoteagent): trim narrating auth test comments
Cut test comments that restated the code/assertions (one block was
duplicated); keep only the intent/why (fail-open, cleanup also
authenticated, credential usable-not-just-present).
* fix(remoteagent): scope, place and bound the A2A credential correctly
Review of the per-request A2A auth wiring surfaced several ways the
credential could reach the wrong place, or fail to reach the right one with
no signal at all.
- Scope the credential to the whole calling identity, not the bare session
id. A session id is caller-supplied, so two users each holding one named
"default" resolved to the same credential. The key is now app name, user
id, session id and the remote agent's name, each percent-encoded and
joined with "/". The agent name is in there because a bearer token is
scoped to an audience too: one provider shared between two remote agents
would otherwise cross their tokens.
- Refuse a security scheme that cannot carry the resolved credential,
returning ErrCredentialNotFound so the interceptor tries the next one. It
picks among the schemes in one requirement object in Go map order and
never checks them against the credential, so a bearer token landed in an
API-key header on a random subset of requests. The check also reads the
fields the interceptor ignores: a query- or cookie-located API key would
go out as a header the remote never reads, and a card asking for Basic
would receive Bearer.
- Refuse a redirect that leaves the card's host or downgrades its scheme,
checked against the original request and the previous hop. The credential
is attached before the first hop and Go replays headers on every hop,
stripping Authorization only when the host changes and never stripping a
card-named API-key header.
- Bound the OAuth2 mint. TokenSource.Token takes no context and the sources
behind auth.ADC and auth.ServiceAccount post through a client with no
timeout, so a hung endpoint held the run loop past every budget around it.
- Keep the cleanup context an agent.InvocationContext. Detaching it stripped
the type a provider is told it can recover, so a provider that did failed
there and the cancel went out unauthenticated — against exactly the
secured agents where cleanup matters.
- Authenticate the CancelTask the adka2a server issues for an abandoned
child task. It reuses the subagent's auth-wired client with no scope on
the context, so a secured remote rejects the cancel and the task leaks.
- Turn a typed-nil provider into a constructor error, accept pointer
credential forms, keep an oauth2.RetrieveError's response body out of the
message the interceptor logs at ERROR, and warn once per agent where
fail-open would otherwise be silent.
- Leave the context untouched when Auth is unset, and export
CredentialScope for the one case Auth cannot serve: a custom
ClientProvider wiring its own interceptor.
Document on the field what the card dictates, what cannot be sent, and how
this differs from the field of the same name on mcptoolset.Config.
* fix(remoteagent): signal every unauthenticated a2a send, and bound the mint
A card declaring a scheme and one empty requirement object has a
requirement list of length 1, so the "no requirement" warning did not
fire, and the a2a interceptor's inner loop over that object's scheme
names had nothing to iterate, so it never asked for a credential. The
request left unauthenticated with nothing logged anywhere, and
`security: [{}]` is how OpenAPI spells "authentication optional", so a
real card can carry it. The check is now that no requirement object
names a scheme at all, which is the condition under which the
interceptor never calls the adapter.
The OAuth2 mint is single-flighted per credential scope. Token() takes
no context and cannot be interrupted, so bounding the caller's wait
releases the caller and leaves the mint running: without the
single-flight every request arriving while a token endpoint hangs
started another one and parked another goroutine. The mint goroutine
also recovers, since a panic there is fatal rather than something the
runner can turn into an error.
reattachInvocation now runs only when Auth is set. It keyed on the
context already being an agent.InvocationContext, which it is on the
unset path too, so an opted-out caller was getting a wrapper in
RemoteTaskCleanupCallback where the field's own doc promises a plain
context.WithoutCancel.
isTypedNil no longer rejects a provider on a nil named map, slice or
channel: a value receiver on one is callable, so rejecting it turned a
working provider into a constructor error. The doc named three kinds
where the code covered six.
Tests for five guards that no test could fail on: the OwnsAuthScope
derivation, the exported CredentialScope driven by distinct literals,
schemeAccepts with no card on the context, the no-requirement warning,
and the executor's card re-attachment — that last one through a
credentials service that reads the card, as production's does, rather
than the in-memory store that resolves on scope and scheme name alone.
Doc corrections: ClientProvider described a credential scope it can
never receive, since Auth and ClientProvider are mutually exclusive, and
redactTokenError claimed the named-error-code branch prints only the
code when it also prints error_description and error_uri.
* fix(remoteagent): bound the a2a token mint and close the redaction bypass
The single-flight added in the previous commit had no attempt deadline, so a
token endpoint that hung once wedged that credential scope for the life of the
process: Token() cannot be interrupted, the map entry was removed only by the
goroutine that could never finish, and every later request for that identity
joined it and waited a fresh full mintTimeout. auth/gcp's provider had already
reached the other design for the same reason, and this adopts it — the bound
belongs to the attempt, a joiner waits its remainder, and a caller arriving
after it has passed retires the attempt and mints again. That also caps what a
card can cost: a card naming N bearer-capable schemes produces N resolutions,
which used to be N full timeouts and is now one.
Two smaller faults on the same path. When the mint landed exactly as the
caller's deadline fired, the select picked between them at random and threw
away about half the tokens that had in fact arrived. And the delete has to be
conditional now that an overdue attempt can be retired, or the abandoned
goroutine evicts its successor's live entry on the way out.
redactTokenError decided from the inner error's fields, which the token source
behind auth.ServiceAccount with an Audience defeats: cloud.google.com/go/auth
returns an *Error that prints the response body itself, wrapping an
*oauth2.RetrieveError whose ErrorCode the adapter has already parsed out of
that body. errors.As found the inner one, saw a named code, declined, and the
wrapper printed the body into a log line at ERROR. It now tests what the
message actually says, so it redacts whatever wrapper is in front.
CardNamesNoScheme guarded on len(SecuritySchemes) == 0 where the interceptor
guards on nil. A card whose JSON says "securitySchemes": {} decodes to an empty
but non-nil map, so the interceptor does ask, and one unauthenticated send was
reported twice. It moves to internal/ so the adka2a cancel path can use it too:
that path was the last silent unauthenticated send, since the remote agent's
own warning does not run in a process that only inherited the abandoned task.
The mismatch warning now dedupes per credential type rather than outright. A
session-aware provider resolves a different credential per user, and one
sync.Once reported the first user's broken configuration and swallowed every
later one.
With Auth set, a card naming a non-loopback http interface warns once per
agent. validateCardInterfaceOrigins already enforces https-or-loopback, but
only on a fetched card, so a static, file-sourced or caller-supplied one
reached the send path unchecked and sent the credential in cleartext silently.
This warns rather than refuses: pointing Auth at a plaintext internal host is a
decision a caller can legitimately make, and not noticing is not.
The Auth doc now says the scope is only as trustworthy as the identity behind
it. An adka2a server with no authenticator synthesizes the user id from the
context id the calling peer chose, so a provider caching per scope would hand
one peer the credential resolved for another.
Tests for the guards none of the above would have been caught by, and for five
the previous round left unpinned: the redirect cap asserted against the
constant under test, the client timeout, the executor's card cache-hit branch,
the scope-ownership rule, and internal/agent/remoteagent, which had no test
file at all. The cross-origin redirect test varied the port rather than the
host, so a hostname-only case joins it. 32 mutations run, all red.
* fix(remoteagent): build the redacted token error, never edit the logged one
Two judges split on the previous commit's redaction. Searching the wrapper's
message for the response body closes the case that prompted it, but only for a
wrapper that prints the body verbatim — one that quotes or re-encodes it slips
past, and a body short enough to occur in the message for an unrelated reason
redacts when nothing leaked. Rather than adjudicate that, the construct is
gone: a response that carried a body always gets a message built here from the
status and the error code and uri, and nothing is copied from whatever the
wrapper wrote.
error_description goes with the body. It is unbounded free text parsed out of
that same body, and the endpoint this redaction exists for puts the client's
own signed assertion in it. The previous commit carried it onto the redacted
message, and its own fixture made that visible without failing: the assertion
appeared in both the body and the description, and the test asserted only on
the whole JSON string. The full error stays reachable through the chain, so
only what gets logged is rebuilt.
The no-scheme warning added to the adka2a cancel path had no test — both
existing fixtures use a card that names a scheme, so the branch was
unreachable. It has one now, in both directions.
TestMintGroupJoinerWaitsTheAttemptsRemainder timed a 225ms sleep against a
200ms bound, and overshooting flipped it into the retire-and-re-mint path and
hard-failed rather than missing softly. It now runs on a 2s budget, checks it
actually joined the attempt before measuring, and says so instead of failing
when the machine was too loaded to time it.
36 mutations, all red.
* fix(remoteagent): make the ClientProvider remediation true for the cleanup cancel
NewA2A's error told a caller combining a custom ClientProvider with their own
interceptor that their client "sees the ADK invocation context on every call".
Since the cleanup re-wrap was gated on Auth, that is false for one call: with
Auth unset the cleanup CancelTask gets a plain detached context, which is what
RemoteTaskCleanupCallback's doc and the opted-out contract promise. A caller
following the message got no scope on that call, so the cancel went out
unauthenticated and the remote task kept running — the failure the cleanup
auth exists to prevent. The code stays and the message changes: the context
the provider receives is the invocation context, so the scope is computed
there and the client attaches it to every call. The ClientProvider field doc
and the cleanup comment say the same. The remediation test now follows the
message literally and drives the run through to the CancelTask, which it
previously never reached.
TestMintGroupSeparatesScopes could not fail on the merge it is named for. Its
two mints ran one after the other, so the second found no entry whatever the
map was keyed on, and keying it on the app segment alone left the whole suite
green. The mints now overlap, on real scopes that share their app, session and
agent segments, and keying on the app, or dropping the user or the agent
segment, each turns it red.
The isTypedNil doc said a nil map, slice or channel with a value receiver
"reads the nil fine". That holds for a map only: indexing a nil slice panics
and receiving from a nil channel blocks. The code is unchanged, since no check
on the kind can tell those cases apart.
Correction to 9a8442a: its message says the attempt deadline cuts a card
naming N bearer-capable schemes from N full mint timeouts to one. It does not.
The interceptor asks for each scheme only after the previous one returned, so
each request arrives after the attempt's deadline, retires it and waits a full
budget of its own — three sequential requests against a hung source take three
timeouts, measured. What the deadline does fix is the permanent wedge.
* feat(remoteagent): apply the A2A credential the way adk-python does
A2AConfig.Auth used a2a-go's AuthInterceptor, which lets the agent card decide
where the credential goes and sends the request unauthenticated when it cannot
be resolved. adk-python's RemoteA2aAgent does neither: its configured auth
scheme writes the header and the card's security section is never read, and a
credential it cannot resolve stops the invocation rather than going out
without one. Both were confirmed by running adk-python 2.10.0 (identical to
main at 044a1ec3 for this code) against a recording transport. A card asking
for an API key in X-Card-Key got Authorization: Bearer and no X-Card-Key, and a
user with no credential got an adk_request_credential event and zero requests.
The credential is now applied by an http.RoundTripper installed on the A2A
client, through the credential's own Apply, the same way mcptoolset.Config.Auth
applies it through auth.Transport. So every credential type works, Basic and
auth.WithHeaders included, a card that declares no security still gets the
credential, and a credential that cannot be resolved or applied fails the call.
That removes the machinery the card-driven design needed: scheme matching, the
card handed through the context, and the no-scheme and mismatch warnings, whose
only job was to report sends that can no longer happen.
Also matching adk-python: the credential is resolved once per invocation and
reused, and the agent card fetch done by NewAgentCardProvider carries it, with
the source's scheme checked before the fetch goes out. Resolving once removes
the per-scheme N×timeout cost a hung token endpoint used to impose. The card
fetch keeps a2a-go's 30s resolver timeout rather than the three-minute RPC one.
Interactive consent is not supported yet. A ConsentRequiredError fails the call
like any other error, where adk-python pauses the invocation to ask the user.
Every blocking credential step, an OAuth2 mint or a credential's own Apply, runs
bounded and single-flighted per scope, so a WithHeaders-wrapped OAuth2
credential cannot hold a request past every deadline, and its token endpoint's
response body is redacted like a bare one's. The cleartext warning is now kept
per interface rather than once per agent.
The adka2a cancel path drops its card cache, and tool/mcptoolset no longer needs
the paragraph explaining how its Auth field differed from this one.
* fix(remoteagent): make the ClientProvider advice hold on the adka2a cancel
NewA2A's error and the ClientProvider doc told a caller that the context their
provider receives is the ADK invocation context. That holds only when the agent
runs. An adka2a server hosting it calls the same provider to cancel an
abandoned child task, with its own request context, which is not an
agent.InvocationContext and carries no scope, because OwnsAuthScope is false for
a custom provider. A provider written to the message returned an error there,
so the child task kept running, or panicked out of Executor.Cleanup on an
unchecked assertion. Both texts now name the second caller and say to check the
assertion, and a test pins what that path hands the provider.
The mintCall comment credited auth/gcp with retiring a hung attempt. auth/gcp
does the opposite and says why: retiring would park a new goroutine every
initTimeout. The two share only the attempt's deadline. The comment now states
the trade this code makes — one parked goroutine per scope per mintTimeout
while an endpoint hangs, against a scope locked out for good, since nothing
promises Token() returns — instead of a reason that is not true.
A test now fails if the transport stops keying a mint or an Apply on the
request's own scope, with two identities overlapping. Before it, keying the
mint on "" left the suite green. Three comments that still described the old
card-driven interceptor or an impossible unscoped request are corrected.
* test(remoteagent): pin five changed lines no test could fail on
A mutator that picks every changed condition and comparison, rather than
letting the author choose, found seven mutants of this change that survived
the whole suite. Each was either a missing test or code with no job left.
- The CancelTask re-wrap in cleanupRemoteTask had no job left. The auth
transport recovers the invocation from the context's values whatever type
the context has, so the call now takes the plain timeout context, as it did
before this change. The re-wrap RemoteTaskCleanupCallback's doc promises
stays.
- WithICDelta with a delta that carries no context, or with no delta, was
never called, so rewriting its && as || left the suite green and would have
dereferenced a nil context.
- RoundTrip's early returns close the request body, and nothing checked it.
- The redirect table had no row where only one half of "http to https" held,
and none mixing an explicit default port with an implicit one.
- The cleartext warning test ran two invocations, which cannot tell "warn the
first time" from "warn every time but the first": both log once. It runs
three.
The mutator now kills all 108 of its mutants of this change.
* fix(remoteagent): correct comments that misdescribe the code, pin the single-flight
A pass over every comment and error message this change adds, each checked
against the code it describes, found statements that did not hold:
- The mintCall comment gave auth/gcp a reason for keeping a hung attempt that
auth/gcp's own ErrClientUnavailable doc rules out. Both face a step that may
never return and pick opposite costs: auth/gcp a permanent lockout, this
group a parked goroutine per scope per mintTimeout.
- reattachInvocation's doc and a test doc still said the cleanup re-wrap
exists for the credential provider. It exists for RemoteTaskCleanupCallback.
- The Auth doc and the transport's doc said a credential always applies
itself, as it does under mcptoolset. A bare OAuth2 credential is minted here
instead. mintGroup's doc said at most one step per scope runs at a time,
which an attempt retired past its deadline makes untrue.
- A redirect "that leaves the card's scheme" is not refused: an upgrade to
https on the same host is allowed. The doc now says what the code does.
- redactTokenError called error_uri a short enumerable field. It is free text
parsed out of the same body as error_description, so it is dropped too, and
the claim that the endpoint puts a signed assertion in error_description,
never verified, is gone.
- Test comments overstated what their test catches, or credited it alone with
catching something others also catch.
The ClientProvider doc now says what a provider can rely on during the adka2a
cancel: CredentialScope cannot be rebuilt there, and only an authenticated
caller is available, through a2asrv.CallContextFrom. The Auth doc names
agent.IdentityFromContext, which is what the auth.CredentialProvider contract
tells a provider to use, and a test pins it on the card fetch and the send.
Tests now fail if the transport stops sharing one mint or one Apply between two
requests for the same scope, if RoundTrip leaves the body open when the
credential fails to apply, and if WithICDelta mishandles a delta that replaces
only the branch.1 parent b531451 commit abb8d81
8 files changed
Lines changed: 4206 additions & 11 deletions
File tree
- agent/remoteagent/v2
- internal/agent/remoteagent
- server/adka2a/v2
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
24 | 25 | | |
25 | 26 | | |
26 | 27 | | |
| 28 | + | |
27 | 29 | | |
28 | 30 | | |
29 | 31 | | |
| |||
32 | 34 | | |
33 | 35 | | |
34 | 36 | | |
| 37 | + | |
35 | 38 | | |
36 | 39 | | |
37 | 40 | | |
| |||
214 | 217 | | |
215 | 218 | | |
216 | 219 | | |
217 | | - | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
218 | 231 | | |
219 | 232 | | |
220 | 233 | | |
| |||
237 | 250 | | |
238 | 251 | | |
239 | 252 | | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
240 | 267 | | |
241 | 268 | | |
242 | 269 | | |
| |||
309 | 336 | | |
310 | 337 | | |
311 | 338 | | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
312 | 360 | | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
313 | 425 | | |
314 | 426 | | |
315 | 427 | | |
316 | 428 | | |
317 | 429 | | |
318 | 430 | | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
319 | 434 | | |
320 | 435 | | |
321 | 436 | | |
| |||
326 | 441 | | |
327 | 442 | | |
328 | 443 | | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
329 | 451 | | |
330 | | - | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
331 | 462 | | |
332 | 463 | | |
333 | 464 | | |
| 465 | + | |
334 | 466 | | |
335 | 467 | | |
336 | 468 | | |
337 | 469 | | |
| 470 | + | |
| 471 | + | |
338 | 472 | | |
339 | 473 | | |
340 | 474 | | |
| |||
363 | 497 | | |
364 | 498 | | |
365 | 499 | | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
366 | 506 | | |
367 | 507 | | |
368 | 508 | | |
369 | 509 | | |
370 | | - | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
371 | 517 | | |
372 | 518 | | |
373 | 519 | | |
374 | 520 | | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
375 | 533 | | |
376 | | - | |
| 534 | + | |
377 | 535 | | |
378 | 536 | | |
379 | 537 | | |
| |||
420 | 578 | | |
421 | 579 | | |
422 | 580 | | |
423 | | - | |
| 581 | + | |
424 | 582 | | |
425 | 583 | | |
426 | 584 | | |
| |||
482 | 640 | | |
483 | 641 | | |
484 | 642 | | |
485 | | - | |
| 643 | + | |
486 | 644 | | |
487 | 645 | | |
488 | 646 | | |
489 | 647 | | |
490 | | - | |
| 648 | + | |
491 | 649 | | |
492 | 650 | | |
493 | 651 | | |
494 | 652 | | |
495 | 653 | | |
496 | 654 | | |
497 | 655 | | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
498 | 659 | | |
499 | 660 | | |
500 | 661 | | |
| |||
517 | 678 | | |
518 | 679 | | |
519 | 680 | | |
520 | | - | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
521 | 695 | | |
522 | 696 | | |
523 | 697 | | |
| |||
527 | 701 | | |
528 | 702 | | |
529 | 703 | | |
530 | | - | |
| 704 | + | |
531 | 705 | | |
532 | 706 | | |
533 | 707 | | |
| |||
0 commit comments