diff --git a/libraries/source-read-sdk/README.md b/libraries/source-read-sdk/README.md index fedc5d1dd..8ea65c363 100644 --- a/libraries/source-read-sdk/README.md +++ b/libraries/source-read-sdk/README.md @@ -59,7 +59,7 @@ provider. ## Regenerate -Requires released Codefly CLI **v0.1.219** (the CI pin). From the repository root: +Use the released Codefly CLI selected by the [CI installer](../../scripts/ci/install-codefly.sh). From the repository root: ```sh codefly generate client --from saas-starter/accounts/authority --name source-read-sdk \ diff --git a/libraries/source-read-sdk/library.codefly.yaml b/libraries/source-read-sdk/library.codefly.yaml index 161cdad77..900bbe397 100644 --- a/libraries/source-read-sdk/library.codefly.yaml +++ b/libraries/source-read-sdk/library.codefly.yaml @@ -12,7 +12,7 @@ sources: module: saas-starter service: accounts endpoint: authority - contract-digest: sha256:606e52170c2e55688ce163eb1d54c347f0d19ac40915d7fb6252f2034b787c0e + contract-digest: sha256:9aaed4fe30672e6c11675034ae204cec14ecbb299d68a3898b65780a88f80657 services: - ModuleCapabilitiesService generated-by: diff --git a/module/CALLABLE_SOURCES.md b/module/CALLABLE_SOURCES.md index 2a5f4623b..e74f8880e 100644 --- a/module/CALLABLE_SOURCES.md +++ b/module/CALLABLE_SOURCES.md @@ -78,6 +78,55 @@ binding are recovered from verified identity and durable attempt metadata. ## Receipts and uncertain outcomes +### Conditional receipt verification (local owner candidate) + +`VerifySourceOperationReceipt` is a proposed additional read, not an accepted +release contract. It takes the complete original `InvokeSourceOperationRequest` +through Connect/gRPC/gRPC-Web or POST +`/v1/datasource-operation-receipts/verify` with the whole request in the body. +It has no input-bearing GET or query binding. Its request and response are secret. + +The caller must preserve exact `input_json` text and supply the original nonempty +`expected_declaration_digest`. The host normalizes the effect header/field exactly +as Invoke does and applies the same 65,536-byte complete normalized protojson +request bound. Matching headers are allowed; conflicting or empty carriers refuse. +Under the verified tenant, the original actor must still hold read authority to +the **stored** source, including current delegation, membership, boundary and +personal-source checks. Supplied source metadata never grants authority. +The method declares failure-only audit for the existing source-scope/delegation +refusal event (`saas.datasource.operation.invoked`); successful receipt reads +emit no new invocation event. This metadata does not turn verification into a +source mutation or add audit records for every validation or lookup failure. + +Only after those checks, the host compares the admitted declaration digest and +the complete normalized request with the permanent attempt's SDK request digest. +This reuses `receipts.RequestDigest` and its `codefly.effect-receipt.request.digest/v1` +format; optional-field presence and exact JSON text participate. There is no second +consumer-side fingerprint. A future digest-format change needs an owner migration +that preserves verification of retained attempts; silently rehashing old markers +would not be compatible. No supplied input is newly persisted or logged. + +A committed result means that invocation and admitted declaration match the +stored attempt, and contains the retained response under its existing 1 MiB wire +bound. It says nothing about output correctness, currentness or indefinite retention. +Mismatch returns a generic FailedPrecondition with no output; omitted or empty +declaration preconditions are InvalidArgument. An absent attempt is NotFound; +a matching attempt without retained output is `unknown` with `{}` output and +never authorizes redispatch. Removed/replaced declarations do not prevent the read; +removed sources or revoked authority can refuse it. Legacy invocations that never +pinned a declaration remain recoverable only through the original Lookup. + +Old servers answer Unimplemented. Consumers must refuse unsupported, missing, +malformed, unknown or failed verification; ordinary Lookup success is not a +substitute. Legacy Lookup's request/response and paired Runnable recovery remain +unchanged. This method neither reads a replacement declaration nor contacts a +provider, and introduces no Runnable, ledger, queue or retry mechanism. Its +actor-bound, bounded retention does not provide permanent cross-reviewer evidence. +Owner acceptance, composition qualification and any durable sharing/retention +contract remain separate prerequisites for adoption. + +### Existing receipt lifecycle + The SDK receipts guard serializes `(tenant, effect_id, method)` and verifies the request digest. Its Postgres reads use the request pool under tenant RLS. `receipts.Record` writes the full response using the exact org transaction that @@ -402,3 +451,18 @@ is not release qualification. The CLI preserves scope slots in operation documents and resolves composition selections during preparation, checking that the called endpoint serves both Invoke and Lookup. A passing generation drift check alone does not qualify the delivery path. + +The conditional receipt candidate additionally has an isolated ingress test. +With the documented CLI on `PATH`, run from accounts/code: + +```sh +GOWORK=off go test -race -tags integration ./pkg/adapters -run '^TestSourceOperationVerifyPostgresIngress$' -count=1 -timeout=15m +``` + +It uses the owner's Codefly dependency harness, Postgres/Vault, real signed +sessions, generated Connect client and HTTP POST gateway. It qualifies real +Invoke persistence, caller response loss, binding refusals, legacy Lookup, +current read access, pruning and receipt-pool reopening. Only outbound provider +transport and the retention clock use test seams. It does not qualify a complete +process restart, module-delegation composition, external gateway/proxy deployment, +TypeScript consumer adoption, or production raw body limits. diff --git a/module/REST_SURFACE.md b/module/REST_SURFACE.md index 3f024258b..45de78a08 100644 --- a/module/REST_SURFACE.md +++ b/module/REST_SURFACE.md @@ -17,11 +17,11 @@ module ceiling. The complete regeneration includes these additions and the calla ## Current surface -The accounts projection contains 175 descriptor routes across 30 services, +The accounts projection contains 176 descriptor routes across 30 services, counted from `generated/rest-surface.json`: -- 18 public routes and 157 authenticated routes; -- 148 OpenAPI paths and 175 operations; +- 18 public routes and 158 authenticated routes; +- 149 OpenAPI paths and 176 operations; - zero internal RPCs; - fifteen explicit non-protobuf extensions loaded by auth-gateway: magic-link request/verification, the billing and Resend email webhooks, the two GitHub @@ -29,7 +29,7 @@ counted from `generated/rest-surface.json`: checkout, free-plan, portal, and the four OAuth 2 authorization-server routes (metadata, authorize validation, authorize grant, token). -The generated runtime therefore authorizes 190 REST routes in total. Descriptor +The generated runtime therefore authorizes 191 REST routes in total. Descriptor routes and extensions remain separate so an extension can never be mistaken for a protobuf procedure or inherit policy by path similarity. @@ -119,6 +119,13 @@ from 327 to 324. ## Regeneration +The local candidate `VerifySourceOperationReceipt` is POST-only at +`/v1/datasource-operation-receipts/verify`, with the complete original Invoke +request in its body. It has no query binding, classifies both directions as secret, +and reuses current stored-source read authority and bounded receipt recovery. +See [conditional verification](./CALLABLE_SOURCES.md#conditional-receipt-verification-local-owner-candidate) +for strict declaration, committed/unknown/refusal semantics and adoption limits. + Run from `module/services/accounts`, with Docker running and a Codefly CLI at or above 0.1.160: diff --git a/module/contracts/api/accounts/authority/contract.binpb b/module/contracts/api/accounts/authority/contract.binpb index 257438d43..8910e49e9 100644 Binary files a/module/contracts/api/accounts/authority/contract.binpb and b/module/contracts/api/accounts/authority/contract.binpb differ diff --git a/module/contracts/api/accounts/authority/proto/saas/accounts/v1/datasource.proto b/module/contracts/api/accounts/authority/proto/saas/accounts/v1/datasource.proto index c95696bd1..9d81a63ab 100644 --- a/module/contracts/api/accounts/authority/proto/saas/accounts/v1/datasource.proto +++ b/module/contracts/api/accounts/authority/proto/saas/accounts/v1/datasource.proto @@ -1662,6 +1662,42 @@ service DatasourceService { }; } + // VerifySourceOperationReceipt conditionally reads retained output using the + // exact original Invoke request, including a nonempty declaration precondition. + // Committed means the normalized request and admitted declaration match the + // stored attempt. Unknown remains unresolved; this method never redispatches. + // Current actor, tenant and stored-source read authority apply before comparison. + // Source-scope refusals emit the existing failure audit; successful reads do not + // record another invocation or change the retained receipt. + // This is independent of the paired Runnable Lookup contract above. + rpc VerifySourceOperationReceipt(InvokeSourceOperationRequest) returns (InvokeSourceOperationResponse) { + option (google.api.http) = { + post: "/v1/datasource-operation-receipts/verify" + body: "*" + }; + option (saas.policy.v1.method_policy) = { + exposure: EXPOSURE_AUTHENTICATED + tenant: TENANT_REQUIREMENT_ORG_MEMBER + permissions: "datasource:read" + scopes: "datasource:read" + resource_bindings: { + request_field: "effect_id" + target: RESOURCE_TARGET_OWNED_RESOURCE + lookup: RESOURCE_LOOKUP_RESOURCE_TO_ORGANIZATION + } + mfa: MFA_REQUIREMENT_NONE + audit: { + events: "saas.datasource.operation.invoked" + emission: AUDIT_EMISSION_FAILURE + } + idempotency: IDEMPOTENCY_REQUIREMENT_FORBIDDEN + rate_limit: RATE_LIMIT_CLASS_STANDARD_READ + request_sensitivity: SENSITIVITY_SECRET + response_sensitivity: SENSITIVITY_SECRET + platform_role: PLATFORM_ROLE_REQUIREMENT_NONE + }; + } + // ListSourceOperations reads the currently admitted declarations under source authority. rpc ListSourceOperations(ListSourceOperationsRequest) returns (ListSourceOperationsResponse) { option (google.api.http) = {get: "/v1/organizations/{org_id}/datasources/{source_id}/operations"}; diff --git a/module/contracts/api/accounts/connect/contract.binpb b/module/contracts/api/accounts/connect/contract.binpb index 257438d43..8910e49e9 100644 Binary files a/module/contracts/api/accounts/connect/contract.binpb and b/module/contracts/api/accounts/connect/contract.binpb differ diff --git a/module/contracts/api/accounts/connect/proto/saas/accounts/v1/datasource.proto b/module/contracts/api/accounts/connect/proto/saas/accounts/v1/datasource.proto index c95696bd1..9d81a63ab 100644 --- a/module/contracts/api/accounts/connect/proto/saas/accounts/v1/datasource.proto +++ b/module/contracts/api/accounts/connect/proto/saas/accounts/v1/datasource.proto @@ -1662,6 +1662,42 @@ service DatasourceService { }; } + // VerifySourceOperationReceipt conditionally reads retained output using the + // exact original Invoke request, including a nonempty declaration precondition. + // Committed means the normalized request and admitted declaration match the + // stored attempt. Unknown remains unresolved; this method never redispatches. + // Current actor, tenant and stored-source read authority apply before comparison. + // Source-scope refusals emit the existing failure audit; successful reads do not + // record another invocation or change the retained receipt. + // This is independent of the paired Runnable Lookup contract above. + rpc VerifySourceOperationReceipt(InvokeSourceOperationRequest) returns (InvokeSourceOperationResponse) { + option (google.api.http) = { + post: "/v1/datasource-operation-receipts/verify" + body: "*" + }; + option (saas.policy.v1.method_policy) = { + exposure: EXPOSURE_AUTHENTICATED + tenant: TENANT_REQUIREMENT_ORG_MEMBER + permissions: "datasource:read" + scopes: "datasource:read" + resource_bindings: { + request_field: "effect_id" + target: RESOURCE_TARGET_OWNED_RESOURCE + lookup: RESOURCE_LOOKUP_RESOURCE_TO_ORGANIZATION + } + mfa: MFA_REQUIREMENT_NONE + audit: { + events: "saas.datasource.operation.invoked" + emission: AUDIT_EMISSION_FAILURE + } + idempotency: IDEMPOTENCY_REQUIREMENT_FORBIDDEN + rate_limit: RATE_LIMIT_CLASS_STANDARD_READ + request_sensitivity: SENSITIVITY_SECRET + response_sensitivity: SENSITIVITY_SECRET + platform_role: PLATFORM_ROLE_REQUIREMENT_NONE + }; + } + // ListSourceOperations reads the currently admitted declarations under source authority. rpc ListSourceOperations(ListSourceOperationsRequest) returns (ListSourceOperationsResponse) { option (google.api.http) = {get: "/v1/organizations/{org_id}/datasources/{source_id}/operations"}; diff --git a/module/contracts/api/catalog.codefly.json b/module/contracts/api/catalog.codefly.json index e65f99eec..662db50af 100644 --- a/module/contracts/api/catalog.codefly.json +++ b/module/contracts/api/catalog.codefly.json @@ -10,7 +10,7 @@ "kind": "protobuf", "package": "saas.accounts.v1", "path": "contracts/api/accounts/authority/contract.binpb", - "digest": "sha256:606e52170c2e55688ce163eb1d54c347f0d19ac40915d7fb6252f2034b787c0e", + "digest": "sha256:9aaed4fe30672e6c11675034ae204cec14ecbb299d68a3898b65780a88f80657", "services": [ { "name": "ModuleCapabilitiesService", @@ -66,7 +66,7 @@ "kind": "protobuf", "package": "", "path": "contracts/api/accounts/connect/contract.binpb", - "digest": "sha256:606e52170c2e55688ce163eb1d54c347f0d19ac40915d7fb6252f2034b787c0e", + "digest": "sha256:9aaed4fe30672e6c11675034ae204cec14ecbb299d68a3898b65780a88f80657", "services": [ { "name": "ArtifactService", @@ -259,7 +259,8 @@ "/saas.accounts.v1.DatasourceService/SyncSource", "/saas.accounts.v1.DatasourceService/UnbindDatasourceGroup", "/saas.accounts.v1.DatasourceService/UpdateSource", - "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain" + "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain", + "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt" ] }, { diff --git a/module/module.package.codefly.yaml b/module/module.package.codefly.yaml index 75742ac6a..359900af5 100644 --- a/module/module.package.codefly.yaml +++ b/module/module.package.codefly.yaml @@ -18,12 +18,12 @@ services: kind: protobuf package: saas.accounts.v1 path: contracts/api/accounts/authority/contract.binpb - digest: sha256:606e52170c2e55688ce163eb1d54c347f0d19ac40915d7fb6252f2034b787c0e + digest: sha256:9aaed4fe30672e6c11675034ae204cec14ecbb299d68a3898b65780a88f80657 - endpoint: connect kind: protobuf package: "" path: contracts/api/accounts/connect/contract.binpb - digest: sha256:606e52170c2e55688ce163eb1d54c347f0d19ac40915d7fb6252f2034b787c0e + digest: sha256:9aaed4fe30672e6c11675034ae204cec14ecbb299d68a3898b65780a88f80657 - name: auth-gateway endpoints: [grpc, rest] api-contracts: diff --git a/module/services/accounts/AUTHZ_MATRIX.md b/module/services/accounts/AUTHZ_MATRIX.md index b508aa4ed..deeacb173 100644 --- a/module/services/accounts/AUTHZ_MATRIX.md +++ b/module/services/accounts/AUTHZ_MATRIX.md @@ -2,7 +2,7 @@ > Generated from protobuf service descriptors and `saas.policy.v1.method_policy`; only the prose description is joined from `pkg/business/introspection.go`. Do not edit by hand. Run `go generate ./pkg/business` from `module/services/accounts/code`. -Inventory: **301 RPCs** across **42 services**. Missing, invalid, or unclassified procedures are denied by both Connect and gRPC interceptors. +Inventory: **302 RPCs** across **42 services**. Missing, invalid, or unclassified procedures are denied by both Connect and gRPC interceptors. The compact tier is retained for compatibility. Guards, resource bindings, audit events, limiter class, and data sensitivity are descriptor-authoritative. Domain handlers may enforce stronger state-dependent rules but may not weaken this floor. @@ -106,6 +106,7 @@ The compact tier is retained for compatibility. Guards, resource bindings, audit | `/saas.accounts.v1.DatasourceService/UnbindDatasourceGroup` | unary | `—` | `org_admin` | exposure=AUTHENTICATED; tenant=ORG_ADMIN | — | org_id → ORGANIZATION/DIRECT_ID | SUCCESS: saas.datasource.group_unbound | FORBIDDEN / STANDARD_WRITE | INTERNAL → CONFIDENTIAL | Remove a provider group binding. | | `/saas.accounts.v1.DatasourceService/UpdateSource` | unary | `—` | `org_admin` | exposure=AUTHENTICATED; tenant=ORG_ADMIN | — | org_id → ORGANIZATION/DIRECT_ID | SUCCESS: saas.datasource.source.updated | FORBIDDEN / STANDARD_WRITE | INTERNAL → CONFIDENTIAL | Change what a connected source fetches — its branch and path and file-type filters — keeping its credential, history and collection. | | `/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain` | unary | `—` | `org_admin` | exposure=AUTHENTICATED; tenant=ORG_ADMIN | — | org_id → ORGANIZATION/DIRECT_ID | SUCCESS: saas.datasource.domain_verified | FORBIDDEN / STANDARD_WRITE | INTERNAL → CONFIDENTIAL | Verify a claimed domain by its DNS TXT record. | +| `/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt` | unary | `POST /v1/datasource-operation-receipts/verify` | `org_member` | exposure=AUTHENTICATED; tenant=ORG_MEMBER | perm=datasource:read; scope=datasource:read | effect_id → OWNED_RESOURCE/RESOURCE_TO_ORGANIZATION | FAILURE: saas.datasource.operation.invoked | FORBIDDEN / STANDARD_READ | SECRET → SECRET | Read a source receipt only when the original invocation and admitted declaration match. | | `/saas.accounts.v1.DelegationService/DecideDelegation` | unary | `POST /v1/delegations/{id}:decide` | `org_admin` | exposure=AUTHENTICATED; tenant=ORG_ADMIN | — | org_id → ORGANIZATION/DIRECT_ID | SUCCESS: saas.delegation.approved, saas.delegation.denied | FORBIDDEN / STANDARD_READ | CONFIDENTIAL → CONFIDENTIAL | Approve or deny a delegation request. | | `/saas.accounts.v1.DelegationService/ListPendingDelegations` | unary | `GET /v1/delegations:pending` | `org_member` | exposure=AUTHENTICATED; tenant=ORG_MEMBER | — | org_id → ORGANIZATION/DIRECT_ID | — | FORBIDDEN / STANDARD_READ | CONFIDENTIAL → CONFIDENTIAL | List pending organization delegations. | | `/saas.accounts.v1.DelegationService/RequestDelegation` | unary | `POST /v1/delegations` | `org_member` | exposure=AUTHENTICATED; tenant=ORG_MEMBER | — | org_id → ORGANIZATION/DIRECT_ID | SUCCESS: saas.delegation.requested, saas.delegation.auto_approved | FORBIDDEN / STANDARD_WRITE | CONFIDENTIAL → CONFIDENTIAL | Request a scoped authority delegation. | @@ -316,7 +317,7 @@ The compact tier is retained for compatibility. Guards, resource bindings, audit - `internal`: 55 - `mfa`: 5 - `org_admin`: 71 -- `org_member`: 57 +- `org_member`: 58 - `platform_admin`: 29 - `public`: 32 diff --git a/module/services/accounts/code/pkg/adapters/rest_registration_catalog_gen.go b/module/services/accounts/code/pkg/adapters/rest_registration_catalog_gen.go index 20f82c23a..abf5a832f 100644 --- a/module/services/accounts/code/pkg/adapters/rest_registration_catalog_gen.go +++ b/module/services/accounts/code/pkg/adapters/rest_registration_catalog_gen.go @@ -114,106 +114,107 @@ func registerCatalogRESTHandlers(ctx context.Context, mux *runtime.ServeMux, end } var catalogRESTExactRoutes = map[string]struct{}{ - "DELETE /v1/record-shares": {}, - "DELETE /v1/role-assignments": {}, - "DELETE /v1/scope-grants": {}, - "GET /v1/.well-known/service-info": {}, - "GET /v1/accessible-scopes": {}, - "GET /v1/acquisition": {}, - "GET /v1/api-keys": {}, - "GET /v1/audit-event-types": {}, - "GET /v1/audit-log": {}, - "GET /v1/audit-log:aggregate": {}, - "GET /v1/auth/.well-known/jwks.json": {}, - "GET /v1/collection-access": {}, - "GET /v1/consent/status": {}, - "GET /v1/delegations:pending": {}, - "GET /v1/installations": {}, - "GET /v1/installations:listAvailable": {}, - "GET /v1/invitations": {}, - "GET /v1/mfa/devices": {}, - "GET /v1/notifications": {}, - "GET /v1/notifications/unread-count": {}, - "GET /v1/organizations": {}, - "GET /v1/platform/admins": {}, - "GET /v1/platform/catalogue": {}, - "GET /v1/platform/events/operations": {}, - "GET /v1/platform/events/subscriptions": {}, - "GET /v1/platform/feature-flags": {}, - "GET /v1/platform/jobs": {}, - "GET /v1/platform/jobs/operations": {}, - "GET /v1/platform/module-envelope": {}, - "GET /v1/platform/sessions": {}, - "GET /v1/platform/users": {}, - "GET /v1/platform/waitlist": {}, - "GET /v1/principals": {}, - "GET /v1/public/plans": {}, - "GET /v1/record-shares": {}, - "GET /v1/role-assignments": {}, - "GET /v1/roles": {}, - "GET /v1/user/settings": {}, - "GET /v1/users": {}, - "GET /v1/users/self": {}, - "GET /v1/users:byEmail": {}, - "GET /v1/users:findByIdentity": {}, - "GET /v1/version": {}, - "GET /v1/webhooks": {}, - "POST /v1/api-keys": {}, - "POST /v1/audit-log:export": {}, - "POST /v1/auth/authenticate": {}, - "POST /v1/auth/clients/authorize": {}, - "POST /v1/auth/clients/validate": {}, - "POST /v1/auth/logout": {}, - "POST /v1/auth/mfa/complete": {}, - "POST /v1/auth/mfa/webauthn/begin": {}, - "POST /v1/auth/mfa/webauthn/complete": {}, - "POST /v1/auth/oauth/begin": {}, - "POST /v1/auth/refresh": {}, - "POST /v1/auth/switch-organization": {}, - "POST /v1/auth/token": {}, - "POST /v1/billing/connect/portal": {}, - "POST /v1/consent/terms": {}, - "POST /v1/delegations": {}, - "POST /v1/gdpr/delete": {}, - "POST /v1/gdpr/export": {}, - "POST /v1/installations": {}, - "POST /v1/invitations": {}, - "POST /v1/invitations:accept": {}, - "POST /v1/invitations:inspect": {}, - "POST /v1/invitations:inspect-id": {}, - "POST /v1/mfa/backup-codes": {}, - "POST /v1/mfa/totp/setup": {}, - "POST /v1/mfa/totp/verify": {}, - "POST /v1/mfa/webauthn/register/begin": {}, - "POST /v1/mfa/webauthn/register/finish": {}, - "POST /v1/notifications:read-all": {}, - "POST /v1/organizations": {}, - "POST /v1/platform/admins": {}, - "POST /v1/platform/impersonation:stop": {}, - "POST /v1/principals:agent": {}, - "POST /v1/record-shares": {}, - "POST /v1/resource-follows": {}, - "POST /v1/resource-follows:unfollow": {}, - "POST /v1/role-assignments": {}, - "POST /v1/roles": {}, - "POST /v1/scope-grants": {}, - "POST /v1/scope-nodes": {}, - "POST /v1/solution-installations": {}, - "POST /v1/solution-installations/review": {}, - "POST /v1/sso/disable": {}, - "POST /v1/sso/setup": {}, - "POST /v1/user/settings": {}, - "POST /v1/users": {}, - "POST /v1/waitlist": {}, - "POST /v1/waitlist:verify": {}, - "POST /v1/webhooks": {}, - "POST /v1/work-contexts:child-session": {}, - "POST /v1/work-contexts:exchange-audience": {}, - "POST /v1/work-contexts:renew": {}, - "POST /v1/work-contexts:root-session": {}, - "POST /v1/work-contexts:task": {}, - "PUT /v1/consent/preferences": {}, - "PUT /v1/platform/module-envelope": {}, + "DELETE /v1/record-shares": {}, + "DELETE /v1/role-assignments": {}, + "DELETE /v1/scope-grants": {}, + "GET /v1/.well-known/service-info": {}, + "GET /v1/accessible-scopes": {}, + "GET /v1/acquisition": {}, + "GET /v1/api-keys": {}, + "GET /v1/audit-event-types": {}, + "GET /v1/audit-log": {}, + "GET /v1/audit-log:aggregate": {}, + "GET /v1/auth/.well-known/jwks.json": {}, + "GET /v1/collection-access": {}, + "GET /v1/consent/status": {}, + "GET /v1/delegations:pending": {}, + "GET /v1/installations": {}, + "GET /v1/installations:listAvailable": {}, + "GET /v1/invitations": {}, + "GET /v1/mfa/devices": {}, + "GET /v1/notifications": {}, + "GET /v1/notifications/unread-count": {}, + "GET /v1/organizations": {}, + "GET /v1/platform/admins": {}, + "GET /v1/platform/catalogue": {}, + "GET /v1/platform/events/operations": {}, + "GET /v1/platform/events/subscriptions": {}, + "GET /v1/platform/feature-flags": {}, + "GET /v1/platform/jobs": {}, + "GET /v1/platform/jobs/operations": {}, + "GET /v1/platform/module-envelope": {}, + "GET /v1/platform/sessions": {}, + "GET /v1/platform/users": {}, + "GET /v1/platform/waitlist": {}, + "GET /v1/principals": {}, + "GET /v1/public/plans": {}, + "GET /v1/record-shares": {}, + "GET /v1/role-assignments": {}, + "GET /v1/roles": {}, + "GET /v1/user/settings": {}, + "GET /v1/users": {}, + "GET /v1/users/self": {}, + "GET /v1/users:byEmail": {}, + "GET /v1/users:findByIdentity": {}, + "GET /v1/version": {}, + "GET /v1/webhooks": {}, + "POST /v1/api-keys": {}, + "POST /v1/audit-log:export": {}, + "POST /v1/auth/authenticate": {}, + "POST /v1/auth/clients/authorize": {}, + "POST /v1/auth/clients/validate": {}, + "POST /v1/auth/logout": {}, + "POST /v1/auth/mfa/complete": {}, + "POST /v1/auth/mfa/webauthn/begin": {}, + "POST /v1/auth/mfa/webauthn/complete": {}, + "POST /v1/auth/oauth/begin": {}, + "POST /v1/auth/refresh": {}, + "POST /v1/auth/switch-organization": {}, + "POST /v1/auth/token": {}, + "POST /v1/billing/connect/portal": {}, + "POST /v1/consent/terms": {}, + "POST /v1/datasource-operation-receipts/verify": {}, + "POST /v1/delegations": {}, + "POST /v1/gdpr/delete": {}, + "POST /v1/gdpr/export": {}, + "POST /v1/installations": {}, + "POST /v1/invitations": {}, + "POST /v1/invitations:accept": {}, + "POST /v1/invitations:inspect": {}, + "POST /v1/invitations:inspect-id": {}, + "POST /v1/mfa/backup-codes": {}, + "POST /v1/mfa/totp/setup": {}, + "POST /v1/mfa/totp/verify": {}, + "POST /v1/mfa/webauthn/register/begin": {}, + "POST /v1/mfa/webauthn/register/finish": {}, + "POST /v1/notifications:read-all": {}, + "POST /v1/organizations": {}, + "POST /v1/platform/admins": {}, + "POST /v1/platform/impersonation:stop": {}, + "POST /v1/principals:agent": {}, + "POST /v1/record-shares": {}, + "POST /v1/resource-follows": {}, + "POST /v1/resource-follows:unfollow": {}, + "POST /v1/role-assignments": {}, + "POST /v1/roles": {}, + "POST /v1/scope-grants": {}, + "POST /v1/scope-nodes": {}, + "POST /v1/solution-installations": {}, + "POST /v1/solution-installations/review": {}, + "POST /v1/sso/disable": {}, + "POST /v1/sso/setup": {}, + "POST /v1/user/settings": {}, + "POST /v1/users": {}, + "POST /v1/waitlist": {}, + "POST /v1/waitlist:verify": {}, + "POST /v1/webhooks": {}, + "POST /v1/work-contexts:child-session": {}, + "POST /v1/work-contexts:exchange-audience": {}, + "POST /v1/work-contexts:renew": {}, + "POST /v1/work-contexts:root-session": {}, + "POST /v1/work-contexts:task": {}, + "PUT /v1/consent/preferences": {}, + "PUT /v1/platform/module-envelope": {}, } type catalogRESTTemplateRoute struct { diff --git a/module/services/accounts/code/pkg/adapters/source_operation_authority.go b/module/services/accounts/code/pkg/adapters/source_operation_authority.go index 970c7a58c..379f67421 100644 --- a/module/services/accounts/code/pkg/adapters/source_operation_authority.go +++ b/module/services/accounts/code/pkg/adapters/source_operation_authority.go @@ -38,6 +38,7 @@ func sourceOperationProcedure(method string) bool { case business.SourceOperationMethod, "/saas.accounts.v1.DatasourceService/ListSourceOperations", "/saas.accounts.v1.DatasourceService/LookupInvokeSourceOperation", + "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt", business.SourceReceiptRetentionMethod, "/saas.accounts.v1.DatasourceService/LookupPruneSourceOperationReceipts", business.ReconcileSourceMethod, diff --git a/module/services/accounts/code/pkg/adapters/source_operation_invoke.go b/module/services/accounts/code/pkg/adapters/source_operation_invoke.go index 0759009c4..79d4c0c5a 100644 --- a/module/services/accounts/code/pkg/adapters/source_operation_invoke.go +++ b/module/services/accounts/code/pkg/adapters/source_operation_invoke.go @@ -1,6 +1,7 @@ package adapters import ( + "bytes" "context" "errors" "net/http" @@ -144,6 +145,48 @@ func (h *datasourceConnectHandler) LookupInvokeSourceOperation(ctx context.Conte if err := Validate(req.Msg); err != nil { return nil, translateGRPCError(err) } + attempt, err := h.authorizedSourceOperationAttempt(ctx, req.Header(), req.Msg.EffectId) + if err != nil { + return nil, err + } + return h.retainedSourceOperationResponse(ctx, attempt) +} + +// VerifySourceOperationReceipt is a conditional read, not an invocation or a +// paired Runnable lookup. Only the stored source can supply its authority. +func (h *datasourceConnectHandler) VerifySourceOperationReceipt(ctx context.Context, req *connect.Request[gen.InvokeSourceOperationRequest]) (*connect.Response[gen.InvokeSourceOperationResponse], error) { + ctx = connectCtx(ctx, req.Header()) + if err := Validate(req.Msg); err != nil { + return nil, translateGRPCError(err) + } + if req.Msg.GetExpectedDeclarationDigest() == "" { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("source declaration precondition required")) + } + effect, err := sourceEffectID(req.Header(), req.Msg.EffectId) + if err != nil { + return nil, err + } + normalized := proto.CloneOf(req.Msg) + normalized.EffectId = effect + if !sourceWireFits(normalized, sourceInputEnvelopeBytes) { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("source input exceeds wire bound")) + } + attempt, err := h.authorizedSourceOperationAttempt(ctx, req.Header(), effect) + if err != nil { + return nil, err + } + digest, err := receipts.RequestDigest(normalized) + if err != nil { + return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("source request invalid")) + } + org, _, _ := auth.VerifiedDatabaseIdentity(ctx) // Established by the shared authorization above. + if normalized.OrgId != org || normalized.OrgId != attempt.OrgID || normalized.SourceId != attempt.SourceID || normalized.Operation != attempt.Operation || normalized.GetExpectedDeclarationDigest() != attempt.DeclarationDigest || !bytes.Equal(digest, attempt.RequestDigest) { + return nil, connect.NewError(connect.CodeFailedPrecondition, errors.New("source receipt verification refused")) + } + return h.retainedSourceOperationResponse(ctx, attempt) +} + +func (h *datasourceConnectHandler) authorizedSourceOperationAttempt(ctx context.Context, header http.Header, field string) (*business.SourceOperationAttempt, error) { actor, err := callerID(ctx) if err != nil { return nil, err @@ -155,7 +198,7 @@ func (h *datasourceConnectHandler) LookupInvokeSourceOperation(ctx context.Conte if err = requireOrgMember(ctx, actor, org); err != nil { return nil, translateGRPCError(err) } - effect, err := sourceEffectID(req.Header(), req.Msg.EffectId) + effect, err := sourceEffectID(header, field) if err != nil { return nil, err } @@ -172,16 +215,21 @@ func (h *datasourceConnectHandler) LookupInvokeSourceOperation(ctx context.Conte if _, _, err = h.svc.SourceOperationAuthority(ctx, actor, org, attempt.SourceID, attempt.Operation, effect, "", true); err != nil { return nil, translateGRPCError(err) } + return attempt, nil +} + +func (h *datasourceConnectHandler) retainedSourceOperationResponse(ctx context.Context, attempt *business.SourceOperationAttempt) (*connect.Response[gen.InvokeSourceOperationResponse], error) { + org, _, _ := auth.VerifiedDatabaseIdentity(ctx) // Keep receipt reads under verified identity. store, _ := h.svc.SourceOperationReceipts() if store == nil { return nil, connect.NewError(connect.CodeUnavailable, errors.New("source receipts unavailable")) } - saved, found, err := store.Lookup(ctx, org, effect, business.SourceOperationMethod) + saved, found, err := store.Lookup(ctx, org, attempt.EffectID, business.SourceOperationMethod) if err != nil { return nil, connect.NewError(connect.CodeUnavailable, errors.New("source receipt unavailable")) } if !found { - return connect.NewResponse(sourceUnknownResponse(effect)), nil + return connect.NewResponse(sourceUnknownResponse(attempt.EffectID)), nil } out := &gen.InvokeSourceOperationResponse{} if proto.Unmarshal(saved.Response, out) != nil || !sourceWireFits(out, sourceOutputEnvelopeBytes) { diff --git a/module/services/accounts/code/pkg/adapters/source_operation_refusal_audit_test.go b/module/services/accounts/code/pkg/adapters/source_operation_refusal_audit_test.go index c562a21bd..6d8ea6641 100644 --- a/module/services/accounts/code/pkg/adapters/source_operation_refusal_audit_test.go +++ b/module/services/accounts/code/pkg/adapters/source_operation_refusal_audit_test.go @@ -3,6 +3,7 @@ package adapters import ( "context" "net/http" + "strings" "testing" "accounts/pkg/auth" @@ -12,6 +13,7 @@ import ( "connectrpc.com/connect" basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" ) // Use the real mint and verifier: a hand-stamped delegation key cannot prove @@ -35,7 +37,7 @@ func TestSourceOperationDelegatedRefusalAudited(t *testing.T) { require.NoError(t, err) require.NoError(t, requireSourceOperationScope(ctx, delegationOrg, borrowed, "invoke")) - for _, method := range []string{"invoke", "lookup", "prune", "lookup-prune"} { + for _, method := range []string{"invoke", "lookup", "verify", "prune", "lookup-prune"} { t.Run(method, func(t *testing.T) { store := &sourceRefusalStore{sourceWireStore: &sourceWireStore{role: gen.OrgRole_ORG_ROLE_ADMIN, attempt: &business.SourceOperationAttempt{OrgID: delegationOrg, ActorID: delegationPerson, SourceID: other, Operation: "read_item", EffectID: "refused-effect"}}} @@ -52,6 +54,8 @@ func TestSourceOperationDelegatedRefusalAudited(t *testing.T) { _, err = h.InvokeSourceOperation(ctx, connect.NewRequest(&gen.InvokeSourceOperationRequest{OrgId: delegationOrg, SourceId: other, Operation: "read_item", InputJson: `{"private":"must not enter audit"}`, EffectId: "refused-effect"})) case "lookup": _, err = h.LookupInvokeSourceOperation(ctx, connect.NewRequest(&gen.LookupInvokeSourceOperationRequest{EffectId: "refused-effect"})) + case "verify": + _, err = h.VerifySourceOperationReceipt(ctx, connect.NewRequest(&gen.InvokeSourceOperationRequest{OrgId: delegationOrg, SourceId: borrowed, Operation: "read_item", InputJson: `{"private":"must not enter audit"}`, EffectId: "refused-effect", ExpectedDeclarationDigest: proto.String("sha256:" + strings.Repeat("a", 64))})) case "prune": _, err = h.PruneSourceOperationReceipts(ctx, connect.NewRequest(&gen.PruneSourceOperationReceiptsRequest{OrgId: delegationOrg, SourceId: other, EffectId: "refused-effect"})) case "lookup-prune": diff --git a/module/services/accounts/code/pkg/adapters/source_operation_verify_integration_test.go b/module/services/accounts/code/pkg/adapters/source_operation_verify_integration_test.go new file mode 100644 index 000000000..72a00db0a --- /dev/null +++ b/module/services/accounts/code/pkg/adapters/source_operation_verify_integration_test.go @@ -0,0 +1,325 @@ +//go:build integration + +package adapters + +import ( + "bytes" + "context" + "crypto/rand" + "io" + "net/http" + "net/http/httptest" + "strings" + "sync/atomic" + "testing" + "time" + + "accounts/internal/testdb" + "accounts/pkg/auth" + ed25519minter "accounts/pkg/auth/ed25519" + pgauth "accounts/pkg/auth/pg" + "accounts/pkg/business" + "accounts/pkg/datasource/apisource" + "accounts/pkg/datasource/connector/connectortest" + "accounts/pkg/datasource/operations" + gen "accounts/pkg/gen/saas/accounts/v1" + "accounts/pkg/gen/saas/accounts/v1/accountsv1connect" + "accounts/pkg/infra" + "accounts/pkg/keyservice" + + "connectrpc.com/connect" + "github.com/codefly-dev/core/sdk" + codefly "github.com/codefly-dev/sdk-go" + "github.com/codefly-dev/sdk-go/receipts" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/proto" +) + +// Uses the same package lock and isolated Codefly dependency lifecycle as the +// business integration suite. No identity-stamping interceptor or seeded +// attempt/digest is involved: signed requests enter the production interceptor. +func TestSourceOperationVerifyPostgresIngress(t *testing.T) { + _, err := testdb.RunWithPackageLock(func() int { + t.Run("isolated owner dependencies", testSourceReceiptIngress) + return 0 + }) + require.NoError(t, err) +} + +func testSourceReceiptIngress(t *testing.T) { + ctx := context.Background() + deps, err := sdk.WithDependencies(ctx, sdk.WithExcludedDependencies("object-storage", "cache"), + sdk.WithNamingScope("receipt-ingress-test"), sdk.WithTimeout(10*time.Minute), sdk.WithSilence("store")) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, deps.Destroy(context.Background())) }) + _, err = codefly.Init(ctx) + require.NoError(t, err) + store, err := infra.NewPostgresStore(ctx) + require.NoError(t, err) + t.Cleanup(store.Close) + svc, err := business.NewService(store) + require.NoError(t, err) + keys, err := keyservice.Load(ctx) + require.NoError(t, err) + svc.SetDatasourceConnector(keys.Cipher, nil, "") + ticket, link := make([]byte, 32), make([]byte, 32) + _, err = rand.Read(ticket) + require.NoError(t, err) + _, err = rand.Read(link) + require.NoError(t, err) + svc.SetDatasourceKeys(ticket, link) + emitter, err := business.NewDurableAuditEmitter(store, store) + require.NoError(t, err) + t.Cleanup(emitter.Close) + svc.SetAuditEmitter(emitter) + require.NoError(t, store.WithControlPlane(ctx, func(ctx context.Context) error { + return store.SyncAuditEventTypes(ctx, business.AuditEventCatalog()) + })) + _, privateKey, err := ed25519minter.GenerateKey() + require.NoError(t, err) + minter := ed25519minter.New(ed25519minter.Config{Issuer: "receipt-test", Audience: "receipt-test"}, privateKey, pgauth.NewSessionStore(store)) + svc.SetJWTMinter(minter) + resolver := pgauth.NewResolver(store) + resolver.SetBootstrapAdminEmail("") + svc.SetIdentityResolver(resolver) + identity := func(name string) *auth.Identity { + id, err := resolver.Resolve(ctx, &auth.Claims{Provider: "email", Subject: name, + Email: name + "@test.invalid", EmailVerified: true}, auth.SignupIntent{OrganizationName: name}) + require.NoError(t, err) + return id + } + owner, other := identity("receipt-owner"), identity("receipt-other") + token := func(id *auth.Identity) string { + pair, err := minter.Mint(ctx, id) + require.NoError(t, err) + return pair.AccessToken + } + ownerToken, otherToken := token(owner), token(other) + org, actor := owner.OrgID.String(), owner.UserID.String() + // The maintained fixture membership path supplies a second real member; + // its signed session is then independently verified by ingress. + require.NoError(t, svc.ConvergeFixtureOrgMember(ctx, &gen.AddOrgMemberRequest{ + OrgId: org, UserId: other.UserID.String(), Role: gen.OrgRole_ORG_ROLE_MEMBER})) + member := *other + member.OrgID, member.OrgRole = owner.OrgID, "member" + member.SessionID = business.NewID() + memberToken := token(&member) + receiptStore, closeReceipts, err := store.NewEffectReceipts() + require.NoError(t, err) + t.Cleanup(func() { closeReceipts() }) + clock := &ingressReceiptClock{Store: receiptStore} + require.NoError(t, svc.ConfigureSourceOperationReceipts(clock)) + previous := service + WithService(svc) + t.Cleanup(func() { WithService(previous) }) + servers, err := NewGrpServer(&Configuration{}) + require.NoError(t, err) + endpoint := serveContractConnectTier(t, servers, "") + client := accountsv1connect.NewDatasourceServiceClient(http.DefaultClient, "http://"+endpoint) + var calls atomic.Int64 + provider := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + calls.Add(1) + require.Equal(t, "Bearer synthetic-provider-credential", r.Header.Get("Authorization")) + _, _ = w.Write([]byte(`{}`)) + })) + t.Cleanup(provider.Close) + // The existing owner test transport seam allows a loopback provider; the + // production egress IP guard is qualified separately by apisource tests. + svc.SetSourceOperationIntegrationClient(func(_ apisource.Config, credential string) business.APIOperationClient { + return ingressProvider{credential: credential} + }) + added, err := client.AddSource(ctx, signedReceiptRequest(&gen.AddSourceRequest{OrgId: org, + Provider: gen.DatasourceProvider_DATASOURCE_PROVIDER_API, + Boundary: &gen.AddSourceRequest_CollectionLabel{CollectionLabel: "Receipt ingress"}, + Credential: "synthetic-provider-credential", + Config: &gen.AddSourceRequest_Api{Api: &gen.ApiDatasourceConfig{BaseUrl: provider.URL, ResourcePath: "/items", + CredentialKind: gen.ApiCredentialKind_API_CREDENTIAL_KIND_BEARER}}}, ownerToken)) + require.NoError(t, err) + source := added.Msg.Datasource + var grant *gen.ScopeGrant + require.NoError(t, store.WithOrgTx(ctx, org, func(ctx context.Context) error { + collections, err := store.ListCollectionAccess(ctx, org, "", 100, []string{"datasource"}) + if err != nil { + return err + } + var path string + for _, collection := range collections { + if collection.GetNode().GetId() == source.BoundaryNodeId { + path = collection.GetNode().GetScopePath() + } + } + require.NotEmpty(t, path) + role := business.NewIDString() + if err := store.CreateRole(ctx, &gen.Role{Id: role, Name: "Receipt operations", OrgId: org, + Permissions: []*gen.Permission{{Resource: "datasource", Action: "invoke"}, {Resource: "datasource", Action: "read"}}}); err != nil { + return err + } + grant = &gen.ScopeGrant{Id: business.NewIDString(), OrgId: org, SubjectId: actor, + SubjectKind: gen.SubjectKind_SUBJECT_KIND_PRINCIPAL, ScopePath: path, RoleId: role, GrantedBy: actor} + return store.GrantScope(ctx, grant) + })) + d := connectortest.OperationDeclaration() + d.Effect, d.Method = operations.Mutation, "POST" + declaration, err := declarationToProto(d) + require.NoError(t, err) + declared, err := client.DeclareSourceOperations(ctx, signedReceiptRequest(&gen.DeclareSourceOperationsRequest{ + OrgId: org, SourceId: source.Id, Operations: []*gen.SourceOperation{declaration}}, ownerToken)) + require.NoError(t, err) + original := &gen.InvokeSourceOperationRequest{OrgId: org, SourceId: source.Id, Operation: d.Name, + EffectId: "ingress-effect", InputJson: `{"id":"one"}`, ExpectedDeclarationDigest: proto.String(declared.Msg.Operations[0].Digest)} + // Consume and discard a successful HTTP response at the caller boundary. + // The caller sees a lost response, while the real Invoke transaction commits. + lostClient := accountsv1connect.NewDatasourceServiceClient(&http.Client{Transport: discardInvokeResponse{}}, "http://"+endpoint) + _, err = lostClient.InvokeSourceOperation(ctx, signedReceiptRequest(original, ownerToken)) + require.Error(t, err) + require.EqualValues(t, 1, calls.Load()) + verified, err := client.VerifySourceOperationReceipt(ctx, signedReceiptRequest(original, ownerToken)) + require.NoError(t, err) + require.Equal(t, "committed", verified.Msg.Receipt.Status) + require.Equal(t, `{}`, verified.Msg.OutputJson) + replayed, err := client.InvokeSourceOperation(ctx, signedReceiptRequest(original, ownerToken)) + require.NoError(t, err) + require.True(t, proto.Equal(verified.Msg, replayed.Msg)) + require.EqualValues(t, 1, calls.Load(), "Invoke replay must not redispatch") + + // Actual generated REST -> gRPC -> Connect path, including production + // Authorization forwarding, uses the same signed session and retained row. + conn, err := grpc.NewClient(endpoint, grpc.WithTransportCredentials(insecure.NewCredentials())) + require.NoError(t, err) + t.Cleanup(func() { _ = conn.Close() }) + mux := runtime.NewServeMux(runtime.WithIncomingHeaderMatcher(restIdentityHeaderMatcher)) + require.NoError(t, gen.RegisterDatasourceServiceHandlerClient(ctx, mux, gen.NewDatasourceServiceClient(conn))) + postEndpoint := httptest.NewServer(mux) + t.Cleanup(postEndpoint.Close) + postVerify := func(request *gen.InvokeSourceOperationRequest, bearer string) *httptest.ResponseRecorder { + wire, err := protojson.Marshal(request) + require.NoError(t, err) + r, err := http.NewRequestWithContext(ctx, http.MethodPost, postEndpoint.URL+"/v1/datasource-operation-receipts/verify", bytes.NewReader(wire)) + require.NoError(t, err) + r.Header.Set("Content-Type", "application/json") + r.Header.Set("Authorization", "Bearer "+bearer) + response, err := postEndpoint.Client().Do(r) + require.NoError(t, err) + defer response.Body.Close() + w := httptest.NewRecorder() + w.WriteHeader(response.StatusCode) + _, err = io.Copy(w, response.Body) + require.NoError(t, err) + return w + } + w := postVerify(original, ownerToken) + require.Equal(t, http.StatusOK, w.Code, w.Body.String()) + for _, change := range []func(*gen.InvokeSourceOperationRequest){ + func(r *gen.InvokeSourceOperationRequest) { r.InputJson = `{"id":"two"}` }, + func(r *gen.InvokeSourceOperationRequest) { + r.ExpectedDeclarationDigest = proto.String("sha256:" + strings.Repeat("a", 64)) + }, + func(r *gen.InvokeSourceOperationRequest) { r.SourceId = business.NewIDString() }, + func(r *gen.InvokeSourceOperationRequest) { r.OrgId = other.OrgID.String() }, + func(r *gen.InvokeSourceOperationRequest) { r.ExpectedDeclarationDigest = nil }, + func(r *gen.InvokeSourceOperationRequest) { + r.InputJson = strings.Repeat(" ", sourceInputEnvelopeBytes+1) + }, + } { + modified := proto.CloneOf(original) + change(modified) + require.Equal(t, http.StatusBadRequest, postVerify(modified, ownerToken).Code) + } + require.Equal(t, http.StatusNotFound, postVerify(original, otherToken).Code) + require.Equal(t, http.StatusBadRequest, postVerify(original, memberToken).Code, "another actor cannot reuse this effect") + require.Equal(t, http.StatusUnauthorized, postVerify(original, "invalid-token").Code) + missing := proto.CloneOf(original) + missing.EffectId = "never-invoked" + require.Equal(t, http.StatusNotFound, postVerify(missing, ownerToken).Code) + legacy, err := client.LookupInvokeSourceOperation(ctx, signedReceiptRequest(&gen.LookupInvokeSourceOperationRequest{EffectId: original.EffectId}, ownerToken)) + require.NoError(t, err) + require.True(t, proto.Equal(verified.Msg, legacy.Msg)) + // Reopen the receipt pool over the same database, then rebuild its guard. + // This is pool/guard restart coverage, not a full process restart claim. + closeReceipts() + reopened, closeReopened, err := store.NewEffectReceipts() + require.NoError(t, err) + t.Cleanup(func() { closeReopened() }) + clock.Store = reopened + require.NoError(t, svc.ConfigureSourceOperationReceipts(clock)) + require.Equal(t, http.StatusOK, postVerify(original, ownerToken).Code) + // Real Invoke persists an old receipt through the existing storage-clock + // seam; public retention removes it while the permanent attempt survives. + clock.before = time.Now().Add(-48 * time.Hour) + expired := proto.CloneOf(original) + expired.EffectId = "expired-effect" + _, err = client.InvokeSourceOperation(ctx, signedReceiptRequest(expired, ownerToken)) + require.NoError(t, err) + clock.before = time.Time{} + _, err = client.PruneSourceOperationReceipts(ctx, signedReceiptRequest(&gen.PruneSourceOperationReceiptsRequest{OrgId: org, SourceId: source.Id, EffectId: "prune-effect"}, ownerToken)) + require.NoError(t, err) + expiredResponse := postVerify(expired, ownerToken) + require.Equal(t, http.StatusOK, expiredResponse.Code) + require.Contains(t, expiredResponse.Body.String(), `"unknown"`) + // Removing the declaration does not erase already committed evidence. + _, err = client.DeclareSourceOperations(ctx, signedReceiptRequest(&gen.DeclareSourceOperationsRequest{OrgId: org, SourceId: source.Id}, ownerToken)) + require.NoError(t, err) + require.Equal(t, http.StatusOK, postVerify(original, ownerToken).Code) + require.NoError(t, store.WithOrgTx(ctx, org, func(ctx context.Context) error { + _, err := store.UpdateRole(ctx, grant.RoleId, org, "Read-only receipt access", []*gen.Permission{{Resource: "datasource", Action: "read"}}) + return err + })) + require.Equal(t, http.StatusOK, postVerify(original, ownerToken).Code, "verification requires read, not invoke") + require.NoError(t, store.WithOrgTx(ctx, org, func(ctx context.Context) error { + return store.RevokeScope(ctx, org, actor, grant.SubjectKind, grant.ScopePath, grant.RoleId) + })) + require.Equal(t, http.StatusForbidden, postVerify(original, ownerToken).Code) + require.EqualValues(t, 2, calls.Load(), "only the two distinct Invoke effects may dispatch") +} + +func signedReceiptRequest[T any](message *T, token string) *connect.Request[T] { + r := connect.NewRequest(message) + r.Header().Set("Authorization", "Bearer "+token) + return r +} + +type ingressReceiptClock struct { + receipts.Store + before time.Time +} + +func (c *ingressReceiptClock) Record(ctx context.Context, tx receipts.Tx, receipt receipts.Receipt) error { + if !c.before.IsZero() { + receipt.CommittedAt = c.before + } + return c.Store.Record(ctx, tx, receipt) +} + +type discardInvokeResponse struct{} + +func (discardInvokeResponse) RoundTrip(r *http.Request) (*http.Response, error) { + response, err := http.DefaultTransport.RoundTrip(r) + if err != nil { + return nil, err + } + _, _ = io.Copy(io.Discard, response.Body) + _ = response.Body.Close() + return nil, io.ErrUnexpectedEOF +} + +type ingressProvider struct{ credential string } + +func (p ingressProvider) Do(ctx context.Context, method, target string, body []byte) (*apisource.Result, error) { + r, err := http.NewRequestWithContext(ctx, method, target, bytes.NewReader(body)) + if err != nil { + return nil, err + } + r.Header.Set("Authorization", "Bearer "+p.credential) + client := &http.Client{Transport: &http.Transport{DisableKeepAlives: true}, Timeout: 30 * time.Second} + response, err := client.Do(r) + if err != nil { + return nil, err + } + defer response.Body.Close() + data, err := io.ReadAll(response.Body) + return &apisource.Result{Body: data, StatusCode: response.StatusCode}, err +} diff --git a/module/services/accounts/code/pkg/adapters/source_operation_verify_test.go b/module/services/accounts/code/pkg/adapters/source_operation_verify_test.go new file mode 100644 index 000000000..93397a2c7 --- /dev/null +++ b/module/services/accounts/code/pkg/adapters/source_operation_verify_test.go @@ -0,0 +1,305 @@ +package adapters + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + "time" + + "accounts/pkg/auth" + "accounts/pkg/business" + "accounts/pkg/datasource/connector/connectortest" + "accounts/pkg/datasource/operations" + gen "accounts/pkg/gen/saas/accounts/v1" + "accounts/pkg/gen/saas/accounts/v1/accountsv1connect" + policyv1 "accounts/pkg/gen/saas/policy/v1" + + "connectrpc.com/connect" + basev0 "github.com/codefly-dev/core/generated/go/codefly/base/v0" + "github.com/codefly-dev/sdk-go/receipts" + "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" + "github.com/stretchr/testify/require" + "golang.org/x/net/http2" + "golang.org/x/net/http2/h2c" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + "google.golang.org/protobuf/proto" +) + +func sourceVerificationFixture(t *testing.T) (*datasourceConnectHandler, context.Context, *gen.InvokeSourceOperationRequest, *sourceWireStore) { + t.Helper() + org, actor, source := "11111111-1111-4111-8111-111111111111", "22222222-2222-4222-8222-222222222222", "33333333-3333-4333-8333-333333333333" + declaration, err := operations.Admit(connectortest.OperationDeclaration()) + require.NoError(t, err) + request := &gen.InvokeSourceOperationRequest{OrgId: org, SourceId: source, Operation: declaration.Name, InputJson: `{"id":"one"}`, EffectId: "effect-one", ExpectedDeclarationDigest: proto.String(declaration.Digest)} + digest, err := receipts.RequestDigest(request) + require.NoError(t, err) + store := &sourceWireStore{allowed: true, declaration: declaration, source: &business.DatasourceSource{ID: source, OrgID: org, Provider: business.DatasourceProviderAPI, API: &business.APIDatasourceConfig{}}, attempt: &business.SourceOperationAttempt{OrgID: org, ActorID: actor, SourceID: source, Operation: declaration.Name, DeclarationDigest: declaration.Digest, EffectID: request.EffectId, RequestDigest: digest}} + svc, err := business.NewService(store) + require.NoError(t, err) + previous, cache := service, orgMembershipCache + service, orgMembershipCache = svc, nil + t.Cleanup(func() { service, orgMembershipCache = previous, cache }) + receiptStore := receipts.NewMemoryStore() + require.NoError(t, svc.ConfigureSourceOperationReceipts(receiptStore)) + svc.SetAuditEmitter(&sourceWireAudit{}) + ctx := stampVerifiedIdentity(t.Context(), actor, org, auth.Assurance{}) + out, err := sourceOperationResponse(&business.SourceOperationResult{Output: []byte(`{"value":"retained"}`), EffectID: request.EffectId, CommittedAt: time.Now(), ProviderStatus: 200}) + require.NoError(t, err) + effectCtx := receipts.WithEffect(ctx, receipts.Effect{ID: request.EffectId, Tenant: org, Method: business.SourceOperationMethod, RequestDigest: digest}) + require.NoError(t, receipts.Record(effectCtx, receiptStore, sourceWireTx{}, out)) + return &datasourceConnectHandler{svc: svc}, ctx, request, store +} + +func TestSourceOperationVerifyExactRequest(t *testing.T) { + h, ctx, original, store := sourceVerificationFixture(t) + for _, tc := range []struct { + name string + change func(*gen.InvokeSourceOperationRequest) + code connect.Code + }{ + {"input", func(r *gen.InvokeSourceOperationRequest) { r.InputJson = `{"id":"two"}` }, connect.CodeFailedPrecondition}, + {"exact text", func(r *gen.InvokeSourceOperationRequest) { r.InputJson = `{ "id":"one"}` }, connect.CodeFailedPrecondition}, + {"source", func(r *gen.InvokeSourceOperationRequest) { r.SourceId = "44444444-4444-4444-8444-444444444444" }, connect.CodeFailedPrecondition}, + {"organization", func(r *gen.InvokeSourceOperationRequest) { r.OrgId = "44444444-4444-4444-8444-444444444444" }, connect.CodeFailedPrecondition}, + {"operation", func(r *gen.InvokeSourceOperationRequest) { r.Operation = "another_operation" }, connect.CodeFailedPrecondition}, + {"effect", func(r *gen.InvokeSourceOperationRequest) { r.EffectId = "another-effect" }, connect.CodeNotFound}, + {"declaration", func(r *gen.InvokeSourceOperationRequest) { + r.ExpectedDeclarationDigest = proto.String("sha256:" + strings.Repeat("0", 64)) + }, connect.CodeFailedPrecondition}, + {"omitted declaration", func(r *gen.InvokeSourceOperationRequest) { r.ExpectedDeclarationDigest = nil }, connect.CodeInvalidArgument}, + {"empty declaration", func(r *gen.InvokeSourceOperationRequest) { r.ExpectedDeclarationDigest = proto.String("") }, connect.CodeInvalidArgument}, + } { + t.Run(tc.name, func(t *testing.T) { + request := proto.CloneOf(original) + tc.change(request) + response, err := h.VerifySourceOperationReceipt(ctx, connect.NewRequest(request)) + require.Equal(t, tc.code, connect.CodeOf(err)) + require.Nil(t, response) + require.NotContains(t, err.Error(), "retained") + }) + } + for _, transport := range []connect.ClientOption{connect.WithProtoJSON(), connect.WithGRPC(), connect.WithGRPCWeb()} { + stamp := connect.UnaryInterceptorFunc(func(next connect.UnaryFunc) connect.UnaryFunc { + return func(_ context.Context, r connect.AnyRequest) (connect.AnyResponse, error) { return next(ctx, r) } + }) + _, handler := accountsv1connect.NewDatasourceServiceHandler(h, connect.WithInterceptors(stamp)) + server := httptest.NewServer(handler) + client := accountsv1connect.NewDatasourceServiceClient(server.Client(), server.URL, transport) + for range 2 { + response, err := client.VerifySourceOperationReceipt(t.Context(), connect.NewRequest(original)) + require.NoError(t, err) + require.Equal(t, "committed", response.Msg.GetReceipt().GetStatus()) + require.JSONEq(t, `{"value":"retained"}`, response.Msg.OutputJson) + } + server.Close() + } + // An older generated server must report unsupported, never ordinary success. + _, oldHandler := accountsv1connect.NewDatasourceServiceHandler(accountsv1connect.UnimplementedDatasourceServiceHandler{}) + old := httptest.NewServer(oldHandler) + defer old.Close() + _, err := accountsv1connect.NewDatasourceServiceClient(old.Client(), old.URL).VerifySourceOperationReceipt(ctx, connect.NewRequest(original)) + require.Equal(t, connect.CodeUnimplemented, connect.CodeOf(err)) + // A legacy marker cannot be promoted by supplying its admitted digest later. + legacy := proto.CloneOf(original) + legacy.ExpectedDeclarationDigest = nil + store.attempt.RequestDigest, err = receipts.RequestDigest(legacy) + require.NoError(t, err) + _, err = h.VerifySourceOperationReceipt(ctx, connect.NewRequest(original)) + require.Equal(t, connect.CodeFailedPrecondition, connect.CodeOf(err)) + _, err = h.LookupInvokeSourceOperation(ctx, connect.NewRequest(&gen.LookupInvokeSourceOperationRequest{EffectId: original.EffectId})) + require.NoError(t, err) +} + +func TestSourceOperationVerifyAuditPolicyMatchesRead(t *testing.T) { + h, ctx, original, _ := sourceVerificationFixture(t) + audit := &sourceWireAudit{} + h.svc.SetAuditEmitter(audit) + response, err := h.VerifySourceOperationReceipt(ctx, connect.NewRequest(original)) + require.NoError(t, err) + require.Equal(t, "committed", response.Msg.GetReceipt().GetStatus()) + require.Empty(t, audit.events, "reading retained output must not record another invocation") + + // Source-scope refusals emit this event through the shared owner path, as + // exercised with a real delegated Work Context by + // TestSourceOperationDelegatedRefusalAudited/verify. + policy, ok := business.LookupRPCPolicy(accountsv1connect.DatasourceServiceVerifySourceOperationReceiptProcedure) + require.True(t, ok) + require.Equal(t, policyv1.AuditEmission_AUDIT_EMISSION_FAILURE, policy.MethodPolicy.GetAudit().GetEmission()) + require.Equal(t, []string{string(business.EventDatasourceOperationInvoked)}, policy.MethodPolicy.GetAudit().GetEvents()) +} + +func TestSourceOperationVerifyCurrentAuthorityAndRetention(t *testing.T) { + h, ctx, original, store := sourceVerificationFixture(t) + // Read-only source authority suffices; the supplied source never grants it. + read := context.WithValue(ctx, sourceOperationContextKey{}, &basev0.WorkContextV1{TenantId: original.OrgId, AuthorityScopes: []*basev0.WorkScopeV1{{ResourceKind: "datasource.sources", ResourceIds: []string{original.SourceId}, Actions: []string{"read"}}}}) + _, err := h.VerifySourceOperationReceipt(read, connect.NewRequest(original)) + require.NoError(t, err) + for _, removed := range []bool{false, true} { + if removed { + store.declaration = operations.Declaration{} + } else { + store.declaration.Digest = "replacement" + } + out, err := h.VerifySourceOperationReceipt(read, connect.NewRequest(original)) + require.NoError(t, err) + require.Equal(t, "committed", out.Msg.Receipt.Status) + } + store.allowed = false + _, err = h.VerifySourceOperationReceipt(read, connect.NewRequest(original)) + require.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err)) + store.allowed = true + store.source.PersonalOwnerUserID = "another-actor" + _, err = h.VerifySourceOperationReceipt(read, connect.NewRequest(original)) + require.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err)) + store.source.PersonalOwnerUserID = "" + other := stampVerifiedIdentity(t.Context(), "44444444-4444-4444-8444-444444444444", original.OrgId, auth.Assurance{}) + _, err = h.VerifySourceOperationReceipt(other, connect.NewRequest(original)) + require.Equal(t, connect.CodeFailedPrecondition, connect.CodeOf(err)) + otherTenant := stampVerifiedIdentity(t.Context(), store.attempt.ActorID, "55555555-5555-4555-8555-555555555555", auth.Assurance{}) + response, err := h.VerifySourceOperationReceipt(otherTenant, connect.NewRequest(original)) + require.Error(t, err) + require.Nil(t, response) + store.source = nil + _, err = h.VerifySourceOperationReceipt(read, connect.NewRequest(original)) + require.Equal(t, connect.CodeFailedPrecondition, connect.CodeOf(err)) + store.source = &business.DatasourceSource{ID: original.SourceId, OrgID: original.OrgId} + // Output expiry leaves the permanent marker and never calls a provider. + require.NoError(t, h.svc.ConfigureSourceOperationReceipts(receipts.NewMemoryStore())) + unknown, err := h.VerifySourceOperationReceipt(read, connect.NewRequest(original)) + require.NoError(t, err) + require.Equal(t, "unknown", unknown.Msg.Receipt.Status) + require.Equal(t, "{}", unknown.Msg.OutputJson) + store.attempt = nil + _, err = h.VerifySourceOperationReceipt(read, connect.NewRequest(original)) + require.Equal(t, connect.CodeNotFound, connect.CodeOf(err)) +} + +func TestSourceOperationVerifyNormalizationAndBounds(t *testing.T) { + h, ctx, original, store := sourceVerificationFixture(t) + request := connect.NewRequest(proto.CloneOf(original)) + request.Msg.EffectId = "" + request.Header().Set(receipts.EffectIDHeaderName, original.EffectId) + request.Header().Set(receipts.IdempotencyKeyHeaderName, original.EffectId) + _, err := h.VerifySourceOperationReceipt(ctx, request) + require.NoError(t, err) + request.Msg.EffectId = "different" + _, err = h.VerifySourceOperationReceipt(ctx, request) + require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) + // Account for the complete normalized envelope, including declaration and + // escaped JSON. At the exact bound the request remains readable. + request = connect.NewRequest(proto.CloneOf(original)) + wire, err := sourceWireJSON.Marshal(request.Msg) + require.NoError(t, err) + request.Msg.InputJson += strings.Repeat(" ", sourceInputEnvelopeBytes-len(wire)) + wire, err = sourceWireJSON.Marshal(request.Msg) + require.NoError(t, err) + require.Len(t, wire, sourceInputEnvelopeBytes) + store.attempt.RequestDigest, err = receipts.RequestDigest(request.Msg) + require.NoError(t, err) + _, err = h.VerifySourceOperationReceipt(ctx, request) + require.NoError(t, err) + request.Msg.InputJson += " " + _, err = h.VerifySourceOperationReceipt(ctx, request) + require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err)) +} + +func TestSourceOperationVerifyPOSTGateway(t *testing.T) { + h, ctx, original, store := sourceVerificationFixture(t) + stamp := connect.UnaryInterceptorFunc(func(next connect.UnaryFunc) connect.UnaryFunc { + return func(_ context.Context, r connect.AnyRequest) (connect.AnyResponse, error) { return next(ctx, r) } + }) + _, handler := accountsv1connect.NewDatasourceServiceHandler(h, connect.WithInterceptors(stamp)) + endpoint := httptest.NewServer(h2c.NewHandler(handler, &http2.Server{})) + defer endpoint.Close() + conn, err := grpc.NewClient(endpoint.Listener.Addr().String(), grpc.WithTransportCredentials(insecure.NewCredentials())) + require.NoError(t, err) + defer conn.Close() + mux := runtime.NewServeMux() + require.NoError(t, gen.RegisterDatasourceServiceHandlerClient(t.Context(), mux, gen.NewDatasourceServiceClient(conn))) + wire, err := sourceWireJSON.Marshal(original) + require.NoError(t, err) + post := httptest.NewRequest(http.MethodPost, "/v1/datasource-operation-receipts/verify", strings.NewReader(string(wire))) + post.Header.Set("Content-Type", "application/json") + response := httptest.NewRecorder() + mux.ServeHTTP(response, post) + require.Equal(t, http.StatusOK, response.Code, response.Body.String()) + require.Contains(t, response.Body.String(), "retained") + bounded := proto.CloneOf(original) + bounded.InputJson += strings.Repeat(" ", sourceInputEnvelopeBytes-len(wire)) + store.attempt.RequestDigest, err = receipts.RequestDigest(bounded) + require.NoError(t, err) + for _, excess := range []bool{false, true} { + if excess { + bounded.InputJson += " " + } + wire, err := sourceWireJSON.Marshal(bounded) + require.NoError(t, err) + post := httptest.NewRequest(http.MethodPost, "/v1/datasource-operation-receipts/verify", strings.NewReader(string(wire))) + post.Header.Set("Content-Type", "application/json") + response := httptest.NewRecorder() + mux.ServeHTTP(response, post) + if excess { + require.Equal(t, http.StatusBadRequest, response.Code) + } else { + require.Equal(t, http.StatusOK, response.Code, response.Body.String()) + } + } + // Body:* admits no query-bound expectation, even beside a valid effect. + query := httptest.NewRequest(http.MethodPost, "/v1/datasource-operation-receipts/verify?input_json=private&effect_id=effect-one", strings.NewReader("{}")) + query.Header.Set("Content-Type", "application/json") + response = httptest.NewRecorder() + previousEmitter := emitHTTPFailureDiagnostic + var diagnostic httpFailureDiagnostic + emitHTTPFailureDiagnostic = func(d httpFailureDiagnostic) { diagnostic = d } + t.Cleanup(func() { emitHTTPFailureDiagnostic = previousEmitter }) + logRequestOutcome(mux).ServeHTTP(response, query) + require.Equal(t, http.StatusBadRequest, response.Code) + require.NotContains(t, response.Body.String(), "private") + require.NotContains(t, response.Body.String(), "retained") + logged, err := json.Marshal(diagnostic) + require.NoError(t, err) + require.NotContains(t, string(logged), "private") + require.NotContains(t, string(logged), "input_json") + // Connect does not expose this secret-bearing read through its GET protocol. + response = httptest.NewRecorder() + handler.ServeHTTP(response, httptest.NewRequest(http.MethodGet, accountsv1connect.DatasourceServiceVerifySourceOperationReceiptProcedure, nil)) + require.Equal(t, http.StatusMethodNotAllowed, response.Code) + policy, ok := business.LookupRPCPolicy(accountsv1connect.DatasourceServiceVerifySourceOperationReceiptProcedure) + require.True(t, ok) + require.Equal(t, policyv1.Sensitivity_SENSITIVITY_SECRET, policy.MethodPolicy.GetRequestSensitivity()) + require.Equal(t, policyv1.Sensitivity_SENSITIVITY_SECRET, policy.MethodPolicy.GetResponseSensitivity()) + openapi, err := os.ReadFile("../../../generated/openapi-raw/api.swagger.json") + require.NoError(t, err) + var spec struct { + Paths map[string]map[string]json.RawMessage `json:"paths"` + } + require.NoError(t, json.Unmarshal(openapi, &spec)) + path := spec.Paths["/v1/datasource-operation-receipts/verify"] + require.Len(t, path, 1) + var operation struct { + Parameters []struct { + In string `json:"in"` + } `json:"parameters"` + } + require.NoError(t, json.Unmarshal(path["post"], &operation)) + require.Len(t, operation.Parameters, 1) + require.Equal(t, "body", operation.Parameters[0].In) +} + +func TestSourceOperationVerifyStoredResponseBound(t *testing.T) { + h, ctx, original, store := sourceVerificationFixture(t) + receiptStore := receipts.NewMemoryStore() + require.NoError(t, h.svc.ConfigureSourceOperationReceipts(receiptStore)) + effectCtx := receipts.WithEffect(ctx, receipts.Effect{ID: original.EffectId, Tenant: original.OrgId, Method: business.SourceOperationMethod, RequestDigest: store.attempt.RequestDigest}) + out := &gen.InvokeSourceOperationResponse{OutputJson: strings.Repeat("x", sourceOutputEnvelopeBytes)} + require.NoError(t, receipts.Record(effectCtx, receiptStore, sourceWireTx{}, out)) + response, err := h.VerifySourceOperationReceipt(ctx, connect.NewRequest(original)) + require.Nil(t, response) + require.Equal(t, connect.CodeInternal, connect.CodeOf(err)) + require.EqualError(t, err, "internal: source receipt invalid") +} diff --git a/module/services/accounts/code/pkg/business/introspection.go b/module/services/accounts/code/pkg/business/introspection.go index 8779160eb..b10e8d9b8 100644 --- a/module/services/accounts/code/pkg/business/introspection.go +++ b/module/services/accounts/code/pkg/business/introspection.go @@ -81,6 +81,7 @@ var rpcDescriptions = withWorkContextConsumerDescriptions(map[string]string{ "DatasourceService/LookupReconcileSource": "Read back the receipt of one reconcile pass.", "DatasourceService/InvokeSourceOperation": "Call a declared source operation under current authority and an effect receipt.", "DatasourceService/LookupInvokeSourceOperation": "Read the current authorized outcome of a source effect without redispatching it.", + "DatasourceService/VerifySourceOperationReceipt": "Read a source receipt only when the original invocation and admitted declaration match.", "DatasourceService/DeclareSourceOperations": "Atomically replace an API source's declared operations as an organization administrator.", "DatasourceService/ListSourceOperations": "Read an API source's declared operations under current source authority.", "ModuleCapabilitiesService/GetCurrentInstallation": "Read current active installation identity as a verified organization member through an authenticated module.", diff --git a/module/services/accounts/code/pkg/business/owned_resource_resolver.go b/module/services/accounts/code/pkg/business/owned_resource_resolver.go index 1fe74523f..4ea98cc39 100644 --- a/module/services/accounts/code/pkg/business/owned_resource_resolver.go +++ b/module/services/accounts/code/pkg/business/owned_resource_resolver.go @@ -37,6 +37,7 @@ var ownedResourceResolvers = map[string]ownedResourceResolver{ SourceReceiptRetentionMethod: resolveSourceOperationOrg, "/saas.accounts.v1.DatasourceService/LookupPruneSourceOperationReceipts": resolveSourceEffectOrg, "/saas.accounts.v1.DatasourceService/LookupInvokeSourceOperation": resolveSourceEffectOrg, + "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt": resolveSourceEffectOrg, "/saas.accounts.v1.DatasourceService/LookupReconcileSource": resolveSourceEffectOrg, "/saas.accounts.v1.DatasourceService/ReconcileSource": resolveSourceOperationOrg, "/saas.accounts.v1.DashboardService/DeleteDashboard": resolveDashboardOrg, diff --git a/module/services/accounts/code/pkg/business/source_operation_integration.go b/module/services/accounts/code/pkg/business/source_operation_integration.go new file mode 100644 index 000000000..4fd5b6175 --- /dev/null +++ b/module/services/accounts/code/pkg/business/source_operation_integration.go @@ -0,0 +1,12 @@ +//go:build integration + +package business + +import "accounts/pkg/datasource/apisource" + +// SetSourceOperationIntegrationClient exposes the existing provider transport +// seam to cross-package integration tests. Identity, authority, admission, +// cipher and persistence remain real. Normal service builds exclude this file. +func (s *Service) SetSourceOperationIntegrationClient(fn func(apisource.Config, string) APIOperationClient) { + s.newAPIOperationClient = fn +} diff --git a/module/services/accounts/code/pkg/cataloggen/api_contract_surfaces_test.go b/module/services/accounts/code/pkg/cataloggen/api_contract_surfaces_test.go index d5ae56b81..1f780e4e7 100644 --- a/module/services/accounts/code/pkg/cataloggen/api_contract_surfaces_test.go +++ b/module/services/accounts/code/pkg/cataloggen/api_contract_surfaces_test.go @@ -48,7 +48,7 @@ func TestAPIContractSurfacesAreDeterministicCurrentAndListenerScoped(t *testing. } } require.Equal(t, business.ModuleAuthorityProcedures(), authority) - for _, method := range []string{"InvokeSourceOperation", "LookupInvokeSourceOperation", "PruneSourceOperationReceipts", "LookupPruneSourceOperationReceipts"} { + for _, method := range []string{"InvokeSourceOperation", "LookupInvokeSourceOperation", "VerifySourceOperationReceipt", "PruneSourceOperationReceipts", "LookupPruneSourceOperationReceipts"} { procedure := "/saas.accounts.v1.DatasourceService/" + method require.NotContains(t, authority, procedure) require.Contains(t, connectProcedures, procedure) diff --git a/module/services/accounts/code/pkg/gen/saas/accounts/v1/accountsv1connect/datasource.connect.go b/module/services/accounts/code/pkg/gen/saas/accounts/v1/accountsv1connect/datasource.connect.go index 773f6797d..4efccf7c0 100644 --- a/module/services/accounts/code/pkg/gen/saas/accounts/v1/accountsv1connect/datasource.connect.go +++ b/module/services/accounts/code/pkg/gen/saas/accounts/v1/accountsv1connect/datasource.connect.go @@ -46,6 +46,9 @@ const ( // DatasourceServiceLookupInvokeSourceOperationProcedure is the fully-qualified name of the // DatasourceService's LookupInvokeSourceOperation RPC. DatasourceServiceLookupInvokeSourceOperationProcedure = "/saas.accounts.v1.DatasourceService/LookupInvokeSourceOperation" + // DatasourceServiceVerifySourceOperationReceiptProcedure is the fully-qualified name of the + // DatasourceService's VerifySourceOperationReceipt RPC. + DatasourceServiceVerifySourceOperationReceiptProcedure = "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt" // DatasourceServiceListSourceOperationsProcedure is the fully-qualified name of the // DatasourceService's ListSourceOperations RPC. DatasourceServiceListSourceOperationsProcedure = "/saas.accounts.v1.DatasourceService/ListSourceOperations" @@ -162,6 +165,15 @@ type DatasourceServiceClient interface { // Once an attempt exists, retained receipt evidence survives declaration // removal or replacement. Current source read authority still applies. LookupInvokeSourceOperation(context.Context, *connect.Request[v1.LookupInvokeSourceOperationRequest]) (*connect.Response[v1.InvokeSourceOperationResponse], error) + // VerifySourceOperationReceipt conditionally reads retained output using the + // exact original Invoke request, including a nonempty declaration precondition. + // Committed means the normalized request and admitted declaration match the + // stored attempt. Unknown remains unresolved; this method never redispatches. + // Current actor, tenant and stored-source read authority apply before comparison. + // Source-scope refusals emit the existing failure audit; successful reads do not + // record another invocation or change the retained receipt. + // This is independent of the paired Runnable Lookup contract above. + VerifySourceOperationReceipt(context.Context, *connect.Request[v1.InvokeSourceOperationRequest]) (*connect.Response[v1.InvokeSourceOperationResponse], error) // ListSourceOperations reads the currently admitted declarations under source authority. ListSourceOperations(context.Context, *connect.Request[v1.ListSourceOperationsRequest]) (*connect.Response[v1.ListSourceOperationsResponse], error) // DeclareSourceOperations atomically replaces the source operation set, as an administrator. @@ -338,6 +350,12 @@ func NewDatasourceServiceClient(httpClient connect.HTTPClient, baseURL string, o connect.WithSchema(datasourceServiceMethods.ByName("LookupInvokeSourceOperation")), connect.WithClientOptions(opts...), ), + verifySourceOperationReceipt: connect.NewClient[v1.InvokeSourceOperationRequest, v1.InvokeSourceOperationResponse]( + httpClient, + baseURL+DatasourceServiceVerifySourceOperationReceiptProcedure, + connect.WithSchema(datasourceServiceMethods.ByName("VerifySourceOperationReceipt")), + connect.WithClientOptions(opts...), + ), listSourceOperations: connect.NewClient[v1.ListSourceOperationsRequest, v1.ListSourceOperationsResponse]( httpClient, baseURL+DatasourceServiceListSourceOperationsProcedure, @@ -527,6 +545,7 @@ type datasourceServiceClient struct { lookupPruneSourceOperationReceipts *connect.Client[v1.LookupPruneSourceOperationReceiptsRequest, v1.PruneSourceOperationReceiptsResponse] invokeSourceOperation *connect.Client[v1.InvokeSourceOperationRequest, v1.InvokeSourceOperationResponse] lookupInvokeSourceOperation *connect.Client[v1.LookupInvokeSourceOperationRequest, v1.InvokeSourceOperationResponse] + verifySourceOperationReceipt *connect.Client[v1.InvokeSourceOperationRequest, v1.InvokeSourceOperationResponse] listSourceOperations *connect.Client[v1.ListSourceOperationsRequest, v1.ListSourceOperationsResponse] declareSourceOperations *connect.Client[v1.DeclareSourceOperationsRequest, v1.DeclareSourceOperationsResponse] addGitHubSource *connect.Client[v1.AddGitHubSourceRequest, v1.AddGitHubSourceResponse] @@ -581,6 +600,12 @@ func (c *datasourceServiceClient) LookupInvokeSourceOperation(ctx context.Contex return c.lookupInvokeSourceOperation.CallUnary(ctx, req) } +// VerifySourceOperationReceipt calls +// saas.accounts.v1.DatasourceService.VerifySourceOperationReceipt. +func (c *datasourceServiceClient) VerifySourceOperationReceipt(ctx context.Context, req *connect.Request[v1.InvokeSourceOperationRequest]) (*connect.Response[v1.InvokeSourceOperationResponse], error) { + return c.verifySourceOperationReceipt.CallUnary(ctx, req) +} + // ListSourceOperations calls saas.accounts.v1.DatasourceService.ListSourceOperations. func (c *datasourceServiceClient) ListSourceOperations(ctx context.Context, req *connect.Request[v1.ListSourceOperationsRequest]) (*connect.Response[v1.ListSourceOperationsResponse], error) { return c.listSourceOperations.CallUnary(ctx, req) @@ -757,6 +782,15 @@ type DatasourceServiceHandler interface { // Once an attempt exists, retained receipt evidence survives declaration // removal or replacement. Current source read authority still applies. LookupInvokeSourceOperation(context.Context, *connect.Request[v1.LookupInvokeSourceOperationRequest]) (*connect.Response[v1.InvokeSourceOperationResponse], error) + // VerifySourceOperationReceipt conditionally reads retained output using the + // exact original Invoke request, including a nonempty declaration precondition. + // Committed means the normalized request and admitted declaration match the + // stored attempt. Unknown remains unresolved; this method never redispatches. + // Current actor, tenant and stored-source read authority apply before comparison. + // Source-scope refusals emit the existing failure audit; successful reads do not + // record another invocation or change the retained receipt. + // This is independent of the paired Runnable Lookup contract above. + VerifySourceOperationReceipt(context.Context, *connect.Request[v1.InvokeSourceOperationRequest]) (*connect.Response[v1.InvokeSourceOperationResponse], error) // ListSourceOperations reads the currently admitted declarations under source authority. ListSourceOperations(context.Context, *connect.Request[v1.ListSourceOperationsRequest]) (*connect.Response[v1.ListSourceOperationsResponse], error) // DeclareSourceOperations atomically replaces the source operation set, as an administrator. @@ -929,6 +963,12 @@ func NewDatasourceServiceHandler(svc DatasourceServiceHandler, opts ...connect.H connect.WithSchema(datasourceServiceMethods.ByName("LookupInvokeSourceOperation")), connect.WithHandlerOptions(opts...), ) + datasourceServiceVerifySourceOperationReceiptHandler := connect.NewUnaryHandler( + DatasourceServiceVerifySourceOperationReceiptProcedure, + svc.VerifySourceOperationReceipt, + connect.WithSchema(datasourceServiceMethods.ByName("VerifySourceOperationReceipt")), + connect.WithHandlerOptions(opts...), + ) datasourceServiceListSourceOperationsHandler := connect.NewUnaryHandler( DatasourceServiceListSourceOperationsProcedure, svc.ListSourceOperations, @@ -1119,6 +1159,8 @@ func NewDatasourceServiceHandler(svc DatasourceServiceHandler, opts ...connect.H datasourceServiceInvokeSourceOperationHandler.ServeHTTP(w, r) case DatasourceServiceLookupInvokeSourceOperationProcedure: datasourceServiceLookupInvokeSourceOperationHandler.ServeHTTP(w, r) + case DatasourceServiceVerifySourceOperationReceiptProcedure: + datasourceServiceVerifySourceOperationReceiptHandler.ServeHTTP(w, r) case DatasourceServiceListSourceOperationsProcedure: datasourceServiceListSourceOperationsHandler.ServeHTTP(w, r) case DatasourceServiceDeclareSourceOperationsProcedure: @@ -1204,6 +1246,10 @@ func (UnimplementedDatasourceServiceHandler) LookupInvokeSourceOperation(context return nil, connect.NewError(connect.CodeUnimplemented, errors.New("saas.accounts.v1.DatasourceService.LookupInvokeSourceOperation is not implemented")) } +func (UnimplementedDatasourceServiceHandler) VerifySourceOperationReceipt(context.Context, *connect.Request[v1.InvokeSourceOperationRequest]) (*connect.Response[v1.InvokeSourceOperationResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("saas.accounts.v1.DatasourceService.VerifySourceOperationReceipt is not implemented")) +} + func (UnimplementedDatasourceServiceHandler) ListSourceOperations(context.Context, *connect.Request[v1.ListSourceOperationsRequest]) (*connect.Response[v1.ListSourceOperationsResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("saas.accounts.v1.DatasourceService.ListSourceOperations is not implemented")) } diff --git a/module/services/accounts/code/pkg/gen/saas/accounts/v1/datasource.pb.go b/module/services/accounts/code/pkg/gen/saas/accounts/v1/datasource.pb.go index 2f590aefa..05cd9406a 100644 --- a/module/services/accounts/code/pkg/gen/saas/accounts/v1/datasource.pb.go +++ b/module/services/accounts/code/pkg/gen/saas/accounts/v1/datasource.pb.go @@ -8121,7 +8121,7 @@ const file_saas_accounts_v1_datasource_proto_rawDesc = "" + "+SOURCE_DELEGATION_REVOCATION_MEMBER_REMOVED\x10\x04\x120\n" + ",SOURCE_DELEGATION_REVOCATION_PERMISSION_LOST\x10\x05\x12.\n" + "*SOURCE_DELEGATION_REVOCATION_USER_INACTIVE\x10\x06\x120\n" + - ",SOURCE_DELEGATION_REVOCATION_BINDING_CHANGED\x10\a2\xb19\n" + + ",SOURCE_DELEGATION_REVOCATION_BINDING_CHANGED\x10\a2\xd7;\n" + "\x11DatasourceService\x12\xb9\x03\n" + "\x1cPruneSourceOperationReceipts\x125.saas.accounts.v1.PruneSourceOperationReceiptsRequest\x1a6.saas.accounts.v1.PruneSourceOperationReceiptsResponse\"\xa9\x02\xc2\xf3\x18l\b\x02\x10\x03\x1a\x11datasource:invoke\"\x11datasource:invoke*\x0f\n" + "\tsource_id\x10\x04\x18\x030\x01:#\n" + @@ -8136,7 +8136,10 @@ const file_saas_accounts_v1_datasource_proto_rawDesc = "" + "\x02\b<\x12\x03\b\xd8\x04\x18\x05\"\x02\b\x02*\vUNAVAILABLE*\x12RESOURCE_EXHAUSTED*\aABORTED2\x0fsaas-datasourceJ?/saas.accounts.v1.DatasourceService/LookupInvokeSourceOperationP\x80\x80\x04X\x80\x80@`\x01r\x18\n" + "\x06source\x12\x06invoke\x12\x04read\x18\x01\x82\xd3\xe4\x93\x02U:\x01*\"P/v1/organizations/{org_id}/datasources/{source_id}/operations/{operation}:invoke\x12\x87\x02\n" + "\x1bLookupInvokeSourceOperation\x124.saas.accounts.v1.LookupInvokeSourceOperationRequest\x1a/.saas.accounts.v1.InvokeSourceOperationResponse\"\x80\x01\xc2\xf3\x18G\b\x02\x10\x03\x1a\x0fdatasource:read\"\x0fdatasource:read*\x0f\n" + - "\teffect_id\x10\x04\x18\x030\x01:\x02\x10\x01@\x01H\x03P\x03X\x04`\x01\x82\xd3\xe4\x93\x02/\x12-/v1/datasource-operation-receipts/{effect_id}\x12\x85\x02\n" + + "\teffect_id\x10\x04\x18\x030\x01:\x02\x10\x01@\x01H\x03P\x03X\x04`\x01\x82\xd3\xe4\x93\x02/\x12-/v1/datasource-operation-receipts/{effect_id}\x12\xa3\x02\n" + + "\x1cVerifySourceOperationReceipt\x12..saas.accounts.v1.InvokeSourceOperationRequest\x1a/.saas.accounts.v1.InvokeSourceOperationResponse\"\xa1\x01\xc2\xf3\x18j\b\x02\x10\x03\x1a\x0fdatasource:read\"\x0fdatasource:read*\x0f\n" + + "\teffect_id\x10\x04\x18\x030\x01:%\n" + + "!saas.datasource.operation.invoked\x10\x03@\x01H\x03P\x04X\x04`\x01\x82\xd3\xe4\x93\x02-:\x01*\"(/v1/datasource-operation-receipts/verify\x12\x85\x02\n" + "\x14ListSourceOperations\x12-.saas.accounts.v1.ListSourceOperationsRequest\x1a..saas.accounts.v1.ListSourceOperationsResponse\"\x8d\x01\xc2\xf3\x18D\b\x02\x10\x03\x1a\x0fdatasource:read\"\x0fdatasource:read*\f\n" + "\x06org_id\x10\x02\x18\x010\x01:\x02\x10\x01@\x01H\x03P\x03X\x03`\x01\x82\xd3\xe4\x93\x02?\x12=/v1/organizations/{org_id}/datasources/{source_id}/operations\x12\x93\x02\n" + "\x17DeclareSourceOperations\x120.saas.accounts.v1.DeclareSourceOperationsRequest\x1a1.saas.accounts.v1.DeclareSourceOperationsResponse\"\x92\x01\xc2\xf3\x18F\b\x02\x10\x04*\f\n" + @@ -8446,72 +8449,74 @@ var file_saas_accounts_v1_datasource_proto_depIdxs = []int32{ 105, // 93: saas.accounts.v1.DatasourceService.LookupPruneSourceOperationReceipts:input_type -> saas.accounts.v1.LookupPruneSourceOperationReceiptsRequest 99, // 94: saas.accounts.v1.DatasourceService.InvokeSourceOperation:input_type -> saas.accounts.v1.InvokeSourceOperationRequest 102, // 95: saas.accounts.v1.DatasourceService.LookupInvokeSourceOperation:input_type -> saas.accounts.v1.LookupInvokeSourceOperationRequest - 97, // 96: saas.accounts.v1.DatasourceService.ListSourceOperations:input_type -> saas.accounts.v1.ListSourceOperationsRequest - 95, // 97: saas.accounts.v1.DatasourceService.DeclareSourceOperations:input_type -> saas.accounts.v1.DeclareSourceOperationsRequest - 20, // 98: saas.accounts.v1.DatasourceService.AddGitHubSource:input_type -> saas.accounts.v1.AddGitHubSourceRequest - 22, // 99: saas.accounts.v1.DatasourceService.AddSource:input_type -> saas.accounts.v1.AddSourceRequest - 24, // 100: saas.accounts.v1.DatasourceService.UpdateSource:input_type -> saas.accounts.v1.UpdateSourceRequest - 30, // 101: saas.accounts.v1.DatasourceService.GetDatasourceCatalog:input_type -> saas.accounts.v1.GetDatasourceCatalogRequest - 32, // 102: saas.accounts.v1.DatasourceService.ListSources:input_type -> saas.accounts.v1.ListSourcesRequest - 34, // 103: saas.accounts.v1.DatasourceService.GetSource:input_type -> saas.accounts.v1.GetSourceRequest - 36, // 104: saas.accounts.v1.DatasourceService.SyncSource:input_type -> saas.accounts.v1.SyncSourceRequest - 38, // 105: saas.accounts.v1.DatasourceService.GetSourceSync:input_type -> saas.accounts.v1.GetSourceSyncRequest - 44, // 106: saas.accounts.v1.DatasourceService.DeleteSource:input_type -> saas.accounts.v1.DeleteSourceRequest - 47, // 107: saas.accounts.v1.DatasourceService.BeginGitHubAppSetup:input_type -> saas.accounts.v1.BeginGitHubAppSetupRequest - 49, // 108: saas.accounts.v1.DatasourceService.CompleteGitHubAppSetup:input_type -> saas.accounts.v1.CompleteGitHubAppSetupRequest - 51, // 109: saas.accounts.v1.DatasourceService.MigrateGitHubSourceToApp:input_type -> saas.accounts.v1.MigrateGitHubSourceToAppRequest - 57, // 110: saas.accounts.v1.DatasourceService.BeginDatasourceAccountLink:input_type -> saas.accounts.v1.BeginDatasourceAccountLinkRequest - 59, // 111: saas.accounts.v1.DatasourceService.CompleteDatasourceAccountLink:input_type -> saas.accounts.v1.CompleteDatasourceAccountLinkRequest - 61, // 112: saas.accounts.v1.DatasourceService.ListMyDatasourceAccountLinks:input_type -> saas.accounts.v1.ListMyDatasourceAccountLinksRequest - 63, // 113: saas.accounts.v1.DatasourceService.DeleteDatasourceAccountLink:input_type -> saas.accounts.v1.DeleteDatasourceAccountLinkRequest - 65, // 114: saas.accounts.v1.DatasourceService.GetDatasourceDirectory:input_type -> saas.accounts.v1.GetDatasourceDirectoryRequest - 67, // 115: saas.accounts.v1.DatasourceService.BindDatasourceGroup:input_type -> saas.accounts.v1.BindDatasourceGroupRequest - 69, // 116: saas.accounts.v1.DatasourceService.UnbindDatasourceGroup:input_type -> saas.accounts.v1.UnbindDatasourceGroupRequest - 71, // 117: saas.accounts.v1.DatasourceService.ClaimDatasourceDomain:input_type -> saas.accounts.v1.ClaimDatasourceDomainRequest - 73, // 118: saas.accounts.v1.DatasourceService.VerifyDatasourceDomain:input_type -> saas.accounts.v1.VerifyDatasourceDomainRequest - 75, // 119: saas.accounts.v1.DatasourceService.DeleteDatasourceDomain:input_type -> saas.accounts.v1.DeleteDatasourceDomainRequest - 78, // 120: saas.accounts.v1.DatasourceService.ListSourceDelegations:input_type -> saas.accounts.v1.ListSourceDelegationsRequest - 80, // 121: saas.accounts.v1.DatasourceService.RevokeSourceDelegation:input_type -> saas.accounts.v1.RevokeSourceDelegationRequest - 87, // 122: saas.accounts.v1.DatasourceService.DelegateSourceTriggers:input_type -> saas.accounts.v1.DelegateSourceTriggersRequest - 89, // 123: saas.accounts.v1.DatasourceService.GetSourceTriggerDeclaration:input_type -> saas.accounts.v1.GetSourceTriggerDeclarationRequest - 91, // 124: saas.accounts.v1.DatasourceService.ReconcileSource:input_type -> saas.accounts.v1.ReconcileSourceRequest - 93, // 125: saas.accounts.v1.DatasourceService.LookupReconcileSource:input_type -> saas.accounts.v1.LookupReconcileSourceRequest - 104, // 126: saas.accounts.v1.DatasourceService.PruneSourceOperationReceipts:output_type -> saas.accounts.v1.PruneSourceOperationReceiptsResponse - 104, // 127: saas.accounts.v1.DatasourceService.LookupPruneSourceOperationReceipts:output_type -> saas.accounts.v1.PruneSourceOperationReceiptsResponse - 101, // 128: saas.accounts.v1.DatasourceService.InvokeSourceOperation:output_type -> saas.accounts.v1.InvokeSourceOperationResponse - 101, // 129: saas.accounts.v1.DatasourceService.LookupInvokeSourceOperation:output_type -> saas.accounts.v1.InvokeSourceOperationResponse - 98, // 130: saas.accounts.v1.DatasourceService.ListSourceOperations:output_type -> saas.accounts.v1.ListSourceOperationsResponse - 96, // 131: saas.accounts.v1.DatasourceService.DeclareSourceOperations:output_type -> saas.accounts.v1.DeclareSourceOperationsResponse - 21, // 132: saas.accounts.v1.DatasourceService.AddGitHubSource:output_type -> saas.accounts.v1.AddGitHubSourceResponse - 23, // 133: saas.accounts.v1.DatasourceService.AddSource:output_type -> saas.accounts.v1.AddSourceResponse - 26, // 134: saas.accounts.v1.DatasourceService.UpdateSource:output_type -> saas.accounts.v1.UpdateSourceResponse - 31, // 135: saas.accounts.v1.DatasourceService.GetDatasourceCatalog:output_type -> saas.accounts.v1.GetDatasourceCatalogResponse - 33, // 136: saas.accounts.v1.DatasourceService.ListSources:output_type -> saas.accounts.v1.ListSourcesResponse - 35, // 137: saas.accounts.v1.DatasourceService.GetSource:output_type -> saas.accounts.v1.GetSourceResponse - 37, // 138: saas.accounts.v1.DatasourceService.SyncSource:output_type -> saas.accounts.v1.SyncSourceResponse - 43, // 139: saas.accounts.v1.DatasourceService.GetSourceSync:output_type -> saas.accounts.v1.GetSourceSyncResponse - 45, // 140: saas.accounts.v1.DatasourceService.DeleteSource:output_type -> saas.accounts.v1.DeleteSourceResponse - 48, // 141: saas.accounts.v1.DatasourceService.BeginGitHubAppSetup:output_type -> saas.accounts.v1.BeginGitHubAppSetupResponse - 50, // 142: saas.accounts.v1.DatasourceService.CompleteGitHubAppSetup:output_type -> saas.accounts.v1.CompleteGitHubAppSetupResponse - 52, // 143: saas.accounts.v1.DatasourceService.MigrateGitHubSourceToApp:output_type -> saas.accounts.v1.MigrateGitHubSourceToAppResponse - 58, // 144: saas.accounts.v1.DatasourceService.BeginDatasourceAccountLink:output_type -> saas.accounts.v1.BeginDatasourceAccountLinkResponse - 60, // 145: saas.accounts.v1.DatasourceService.CompleteDatasourceAccountLink:output_type -> saas.accounts.v1.CompleteDatasourceAccountLinkResponse - 62, // 146: saas.accounts.v1.DatasourceService.ListMyDatasourceAccountLinks:output_type -> saas.accounts.v1.ListMyDatasourceAccountLinksResponse - 64, // 147: saas.accounts.v1.DatasourceService.DeleteDatasourceAccountLink:output_type -> saas.accounts.v1.DeleteDatasourceAccountLinkResponse - 66, // 148: saas.accounts.v1.DatasourceService.GetDatasourceDirectory:output_type -> saas.accounts.v1.GetDatasourceDirectoryResponse - 68, // 149: saas.accounts.v1.DatasourceService.BindDatasourceGroup:output_type -> saas.accounts.v1.BindDatasourceGroupResponse - 70, // 150: saas.accounts.v1.DatasourceService.UnbindDatasourceGroup:output_type -> saas.accounts.v1.UnbindDatasourceGroupResponse - 72, // 151: saas.accounts.v1.DatasourceService.ClaimDatasourceDomain:output_type -> saas.accounts.v1.ClaimDatasourceDomainResponse - 74, // 152: saas.accounts.v1.DatasourceService.VerifyDatasourceDomain:output_type -> saas.accounts.v1.VerifyDatasourceDomainResponse - 76, // 153: saas.accounts.v1.DatasourceService.DeleteDatasourceDomain:output_type -> saas.accounts.v1.DeleteDatasourceDomainResponse - 79, // 154: saas.accounts.v1.DatasourceService.ListSourceDelegations:output_type -> saas.accounts.v1.ListSourceDelegationsResponse - 81, // 155: saas.accounts.v1.DatasourceService.RevokeSourceDelegation:output_type -> saas.accounts.v1.RevokeSourceDelegationResponse - 88, // 156: saas.accounts.v1.DatasourceService.DelegateSourceTriggers:output_type -> saas.accounts.v1.DelegateSourceTriggersResponse - 90, // 157: saas.accounts.v1.DatasourceService.GetSourceTriggerDeclaration:output_type -> saas.accounts.v1.GetSourceTriggerDeclarationResponse - 92, // 158: saas.accounts.v1.DatasourceService.ReconcileSource:output_type -> saas.accounts.v1.ReconcileSourceResponse - 92, // 159: saas.accounts.v1.DatasourceService.LookupReconcileSource:output_type -> saas.accounts.v1.ReconcileSourceResponse - 126, // [126:160] is the sub-list for method output_type - 92, // [92:126] is the sub-list for method input_type + 99, // 96: saas.accounts.v1.DatasourceService.VerifySourceOperationReceipt:input_type -> saas.accounts.v1.InvokeSourceOperationRequest + 97, // 97: saas.accounts.v1.DatasourceService.ListSourceOperations:input_type -> saas.accounts.v1.ListSourceOperationsRequest + 95, // 98: saas.accounts.v1.DatasourceService.DeclareSourceOperations:input_type -> saas.accounts.v1.DeclareSourceOperationsRequest + 20, // 99: saas.accounts.v1.DatasourceService.AddGitHubSource:input_type -> saas.accounts.v1.AddGitHubSourceRequest + 22, // 100: saas.accounts.v1.DatasourceService.AddSource:input_type -> saas.accounts.v1.AddSourceRequest + 24, // 101: saas.accounts.v1.DatasourceService.UpdateSource:input_type -> saas.accounts.v1.UpdateSourceRequest + 30, // 102: saas.accounts.v1.DatasourceService.GetDatasourceCatalog:input_type -> saas.accounts.v1.GetDatasourceCatalogRequest + 32, // 103: saas.accounts.v1.DatasourceService.ListSources:input_type -> saas.accounts.v1.ListSourcesRequest + 34, // 104: saas.accounts.v1.DatasourceService.GetSource:input_type -> saas.accounts.v1.GetSourceRequest + 36, // 105: saas.accounts.v1.DatasourceService.SyncSource:input_type -> saas.accounts.v1.SyncSourceRequest + 38, // 106: saas.accounts.v1.DatasourceService.GetSourceSync:input_type -> saas.accounts.v1.GetSourceSyncRequest + 44, // 107: saas.accounts.v1.DatasourceService.DeleteSource:input_type -> saas.accounts.v1.DeleteSourceRequest + 47, // 108: saas.accounts.v1.DatasourceService.BeginGitHubAppSetup:input_type -> saas.accounts.v1.BeginGitHubAppSetupRequest + 49, // 109: saas.accounts.v1.DatasourceService.CompleteGitHubAppSetup:input_type -> saas.accounts.v1.CompleteGitHubAppSetupRequest + 51, // 110: saas.accounts.v1.DatasourceService.MigrateGitHubSourceToApp:input_type -> saas.accounts.v1.MigrateGitHubSourceToAppRequest + 57, // 111: saas.accounts.v1.DatasourceService.BeginDatasourceAccountLink:input_type -> saas.accounts.v1.BeginDatasourceAccountLinkRequest + 59, // 112: saas.accounts.v1.DatasourceService.CompleteDatasourceAccountLink:input_type -> saas.accounts.v1.CompleteDatasourceAccountLinkRequest + 61, // 113: saas.accounts.v1.DatasourceService.ListMyDatasourceAccountLinks:input_type -> saas.accounts.v1.ListMyDatasourceAccountLinksRequest + 63, // 114: saas.accounts.v1.DatasourceService.DeleteDatasourceAccountLink:input_type -> saas.accounts.v1.DeleteDatasourceAccountLinkRequest + 65, // 115: saas.accounts.v1.DatasourceService.GetDatasourceDirectory:input_type -> saas.accounts.v1.GetDatasourceDirectoryRequest + 67, // 116: saas.accounts.v1.DatasourceService.BindDatasourceGroup:input_type -> saas.accounts.v1.BindDatasourceGroupRequest + 69, // 117: saas.accounts.v1.DatasourceService.UnbindDatasourceGroup:input_type -> saas.accounts.v1.UnbindDatasourceGroupRequest + 71, // 118: saas.accounts.v1.DatasourceService.ClaimDatasourceDomain:input_type -> saas.accounts.v1.ClaimDatasourceDomainRequest + 73, // 119: saas.accounts.v1.DatasourceService.VerifyDatasourceDomain:input_type -> saas.accounts.v1.VerifyDatasourceDomainRequest + 75, // 120: saas.accounts.v1.DatasourceService.DeleteDatasourceDomain:input_type -> saas.accounts.v1.DeleteDatasourceDomainRequest + 78, // 121: saas.accounts.v1.DatasourceService.ListSourceDelegations:input_type -> saas.accounts.v1.ListSourceDelegationsRequest + 80, // 122: saas.accounts.v1.DatasourceService.RevokeSourceDelegation:input_type -> saas.accounts.v1.RevokeSourceDelegationRequest + 87, // 123: saas.accounts.v1.DatasourceService.DelegateSourceTriggers:input_type -> saas.accounts.v1.DelegateSourceTriggersRequest + 89, // 124: saas.accounts.v1.DatasourceService.GetSourceTriggerDeclaration:input_type -> saas.accounts.v1.GetSourceTriggerDeclarationRequest + 91, // 125: saas.accounts.v1.DatasourceService.ReconcileSource:input_type -> saas.accounts.v1.ReconcileSourceRequest + 93, // 126: saas.accounts.v1.DatasourceService.LookupReconcileSource:input_type -> saas.accounts.v1.LookupReconcileSourceRequest + 104, // 127: saas.accounts.v1.DatasourceService.PruneSourceOperationReceipts:output_type -> saas.accounts.v1.PruneSourceOperationReceiptsResponse + 104, // 128: saas.accounts.v1.DatasourceService.LookupPruneSourceOperationReceipts:output_type -> saas.accounts.v1.PruneSourceOperationReceiptsResponse + 101, // 129: saas.accounts.v1.DatasourceService.InvokeSourceOperation:output_type -> saas.accounts.v1.InvokeSourceOperationResponse + 101, // 130: saas.accounts.v1.DatasourceService.LookupInvokeSourceOperation:output_type -> saas.accounts.v1.InvokeSourceOperationResponse + 101, // 131: saas.accounts.v1.DatasourceService.VerifySourceOperationReceipt:output_type -> saas.accounts.v1.InvokeSourceOperationResponse + 98, // 132: saas.accounts.v1.DatasourceService.ListSourceOperations:output_type -> saas.accounts.v1.ListSourceOperationsResponse + 96, // 133: saas.accounts.v1.DatasourceService.DeclareSourceOperations:output_type -> saas.accounts.v1.DeclareSourceOperationsResponse + 21, // 134: saas.accounts.v1.DatasourceService.AddGitHubSource:output_type -> saas.accounts.v1.AddGitHubSourceResponse + 23, // 135: saas.accounts.v1.DatasourceService.AddSource:output_type -> saas.accounts.v1.AddSourceResponse + 26, // 136: saas.accounts.v1.DatasourceService.UpdateSource:output_type -> saas.accounts.v1.UpdateSourceResponse + 31, // 137: saas.accounts.v1.DatasourceService.GetDatasourceCatalog:output_type -> saas.accounts.v1.GetDatasourceCatalogResponse + 33, // 138: saas.accounts.v1.DatasourceService.ListSources:output_type -> saas.accounts.v1.ListSourcesResponse + 35, // 139: saas.accounts.v1.DatasourceService.GetSource:output_type -> saas.accounts.v1.GetSourceResponse + 37, // 140: saas.accounts.v1.DatasourceService.SyncSource:output_type -> saas.accounts.v1.SyncSourceResponse + 43, // 141: saas.accounts.v1.DatasourceService.GetSourceSync:output_type -> saas.accounts.v1.GetSourceSyncResponse + 45, // 142: saas.accounts.v1.DatasourceService.DeleteSource:output_type -> saas.accounts.v1.DeleteSourceResponse + 48, // 143: saas.accounts.v1.DatasourceService.BeginGitHubAppSetup:output_type -> saas.accounts.v1.BeginGitHubAppSetupResponse + 50, // 144: saas.accounts.v1.DatasourceService.CompleteGitHubAppSetup:output_type -> saas.accounts.v1.CompleteGitHubAppSetupResponse + 52, // 145: saas.accounts.v1.DatasourceService.MigrateGitHubSourceToApp:output_type -> saas.accounts.v1.MigrateGitHubSourceToAppResponse + 58, // 146: saas.accounts.v1.DatasourceService.BeginDatasourceAccountLink:output_type -> saas.accounts.v1.BeginDatasourceAccountLinkResponse + 60, // 147: saas.accounts.v1.DatasourceService.CompleteDatasourceAccountLink:output_type -> saas.accounts.v1.CompleteDatasourceAccountLinkResponse + 62, // 148: saas.accounts.v1.DatasourceService.ListMyDatasourceAccountLinks:output_type -> saas.accounts.v1.ListMyDatasourceAccountLinksResponse + 64, // 149: saas.accounts.v1.DatasourceService.DeleteDatasourceAccountLink:output_type -> saas.accounts.v1.DeleteDatasourceAccountLinkResponse + 66, // 150: saas.accounts.v1.DatasourceService.GetDatasourceDirectory:output_type -> saas.accounts.v1.GetDatasourceDirectoryResponse + 68, // 151: saas.accounts.v1.DatasourceService.BindDatasourceGroup:output_type -> saas.accounts.v1.BindDatasourceGroupResponse + 70, // 152: saas.accounts.v1.DatasourceService.UnbindDatasourceGroup:output_type -> saas.accounts.v1.UnbindDatasourceGroupResponse + 72, // 153: saas.accounts.v1.DatasourceService.ClaimDatasourceDomain:output_type -> saas.accounts.v1.ClaimDatasourceDomainResponse + 74, // 154: saas.accounts.v1.DatasourceService.VerifyDatasourceDomain:output_type -> saas.accounts.v1.VerifyDatasourceDomainResponse + 76, // 155: saas.accounts.v1.DatasourceService.DeleteDatasourceDomain:output_type -> saas.accounts.v1.DeleteDatasourceDomainResponse + 79, // 156: saas.accounts.v1.DatasourceService.ListSourceDelegations:output_type -> saas.accounts.v1.ListSourceDelegationsResponse + 81, // 157: saas.accounts.v1.DatasourceService.RevokeSourceDelegation:output_type -> saas.accounts.v1.RevokeSourceDelegationResponse + 88, // 158: saas.accounts.v1.DatasourceService.DelegateSourceTriggers:output_type -> saas.accounts.v1.DelegateSourceTriggersResponse + 90, // 159: saas.accounts.v1.DatasourceService.GetSourceTriggerDeclaration:output_type -> saas.accounts.v1.GetSourceTriggerDeclarationResponse + 92, // 160: saas.accounts.v1.DatasourceService.ReconcileSource:output_type -> saas.accounts.v1.ReconcileSourceResponse + 92, // 161: saas.accounts.v1.DatasourceService.LookupReconcileSource:output_type -> saas.accounts.v1.ReconcileSourceResponse + 127, // [127:162] is the sub-list for method output_type + 92, // [92:127] is the sub-list for method input_type 92, // [92:92] is the sub-list for extension type_name 92, // [92:92] is the sub-list for extension extendee 0, // [0:92] is the sub-list for field type_name diff --git a/module/services/accounts/code/pkg/gen/saas/accounts/v1/datasource.pb.gw.go b/module/services/accounts/code/pkg/gen/saas/accounts/v1/datasource.pb.gw.go index 4d1d70321..98b0ff851 100644 --- a/module/services/accounts/code/pkg/gen/saas/accounts/v1/datasource.pb.gw.go +++ b/module/services/accounts/code/pkg/gen/saas/accounts/v1/datasource.pb.gw.go @@ -151,6 +151,33 @@ func local_request_DatasourceService_LookupInvokeSourceOperation_0(ctx context.C return msg, metadata, err } +func request_DatasourceService_VerifySourceOperationReceipt_0(ctx context.Context, marshaler runtime.Marshaler, client DatasourceServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq InvokeSourceOperationRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if req.Body != nil { + _, _ = io.Copy(io.Discard, req.Body) + } + msg, err := client.VerifySourceOperationReceipt(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err +} + +func local_request_DatasourceService_VerifySourceOperationReceipt_0(ctx context.Context, marshaler runtime.Marshaler, server DatasourceServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var ( + protoReq InvokeSourceOperationRequest + metadata runtime.ServerMetadata + ) + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil && !errors.Is(err, io.EOF) { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + msg, err := server.VerifySourceOperationReceipt(ctx, &protoReq) + return msg, metadata, err +} + func request_DatasourceService_ListSourceOperations_0(ctx context.Context, marshaler runtime.Marshaler, client DatasourceServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var ( protoReq ListSourceOperationsRequest @@ -313,6 +340,26 @@ func RegisterDatasourceServiceHandlerServer(ctx context.Context, mux *runtime.Se } forward_DatasourceService_LookupInvokeSourceOperation_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodPost, pattern_DatasourceService_VerifySourceOperationReceipt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateIncomingContext(ctx, mux, req, "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt", runtime.WithHTTPPathPattern("/v1/datasource-operation-receipts/verify")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_DatasourceService_VerifySourceOperationReceipt_0(annotatedContext, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_DatasourceService_VerifySourceOperationReceipt_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) mux.Handle(http.MethodGet, pattern_DatasourceService_ListSourceOperations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -427,6 +474,23 @@ func RegisterDatasourceServiceHandlerClient(ctx context.Context, mux *runtime.Se } forward_DatasourceService_LookupInvokeSourceOperation_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) }) + mux.Handle(http.MethodPost, pattern_DatasourceService_VerifySourceOperationReceipt_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt", runtime.WithHTTPPathPattern("/v1/datasource-operation-receipts/verify")) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_DatasourceService_VerifySourceOperationReceipt_0(annotatedContext, inboundMarshaler, client, req, pathParams) + annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md) + if err != nil { + runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err) + return + } + forward_DatasourceService_VerifySourceOperationReceipt_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + }) mux.Handle(http.MethodGet, pattern_DatasourceService_ListSourceOperations_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -465,15 +529,17 @@ func RegisterDatasourceServiceHandlerClient(ctx context.Context, mux *runtime.Se } var ( - pattern_DatasourceService_InvokeSourceOperation_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5, 1, 0, 4, 1, 5, 6}, []string{"v1", "organizations", "org_id", "datasources", "source_id", "operations", "operation"}, "invoke")) - pattern_DatasourceService_LookupInvokeSourceOperation_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "datasource-operation-receipts", "effect_id"}, "")) - pattern_DatasourceService_ListSourceOperations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"v1", "organizations", "org_id", "datasources", "source_id", "operations"}, "")) - pattern_DatasourceService_DeclareSourceOperations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"v1", "organizations", "org_id", "datasources", "source_id", "operations"}, "")) + pattern_DatasourceService_InvokeSourceOperation_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5, 1, 0, 4, 1, 5, 6}, []string{"v1", "organizations", "org_id", "datasources", "source_id", "operations", "operation"}, "invoke")) + pattern_DatasourceService_LookupInvokeSourceOperation_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"v1", "datasource-operation-receipts", "effect_id"}, "")) + pattern_DatasourceService_VerifySourceOperationReceipt_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"v1", "datasource-operation-receipts", "verify"}, "")) + pattern_DatasourceService_ListSourceOperations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"v1", "organizations", "org_id", "datasources", "source_id", "operations"}, "")) + pattern_DatasourceService_DeclareSourceOperations_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2, 2, 3, 1, 0, 4, 1, 5, 4, 2, 5}, []string{"v1", "organizations", "org_id", "datasources", "source_id", "operations"}, "")) ) var ( - forward_DatasourceService_InvokeSourceOperation_0 = runtime.ForwardResponseMessage - forward_DatasourceService_LookupInvokeSourceOperation_0 = runtime.ForwardResponseMessage - forward_DatasourceService_ListSourceOperations_0 = runtime.ForwardResponseMessage - forward_DatasourceService_DeclareSourceOperations_0 = runtime.ForwardResponseMessage + forward_DatasourceService_InvokeSourceOperation_0 = runtime.ForwardResponseMessage + forward_DatasourceService_LookupInvokeSourceOperation_0 = runtime.ForwardResponseMessage + forward_DatasourceService_VerifySourceOperationReceipt_0 = runtime.ForwardResponseMessage + forward_DatasourceService_ListSourceOperations_0 = runtime.ForwardResponseMessage + forward_DatasourceService_DeclareSourceOperations_0 = runtime.ForwardResponseMessage ) diff --git a/module/services/accounts/code/pkg/gen/saas/accounts/v1/datasource_grpc.pb.go b/module/services/accounts/code/pkg/gen/saas/accounts/v1/datasource_grpc.pb.go index acb01ed49..b7699edf6 100644 --- a/module/services/accounts/code/pkg/gen/saas/accounts/v1/datasource_grpc.pb.go +++ b/module/services/accounts/code/pkg/gen/saas/accounts/v1/datasource_grpc.pb.go @@ -24,6 +24,7 @@ const ( DatasourceService_LookupPruneSourceOperationReceipts_FullMethodName = "/saas.accounts.v1.DatasourceService/LookupPruneSourceOperationReceipts" DatasourceService_InvokeSourceOperation_FullMethodName = "/saas.accounts.v1.DatasourceService/InvokeSourceOperation" DatasourceService_LookupInvokeSourceOperation_FullMethodName = "/saas.accounts.v1.DatasourceService/LookupInvokeSourceOperation" + DatasourceService_VerifySourceOperationReceipt_FullMethodName = "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt" DatasourceService_ListSourceOperations_FullMethodName = "/saas.accounts.v1.DatasourceService/ListSourceOperations" DatasourceService_DeclareSourceOperations_FullMethodName = "/saas.accounts.v1.DatasourceService/DeclareSourceOperations" DatasourceService_AddGitHubSource_FullMethodName = "/saas.accounts.v1.DatasourceService/AddGitHubSource" @@ -82,6 +83,15 @@ type DatasourceServiceClient interface { // Once an attempt exists, retained receipt evidence survives declaration // removal or replacement. Current source read authority still applies. LookupInvokeSourceOperation(ctx context.Context, in *LookupInvokeSourceOperationRequest, opts ...grpc.CallOption) (*InvokeSourceOperationResponse, error) + // VerifySourceOperationReceipt conditionally reads retained output using the + // exact original Invoke request, including a nonempty declaration precondition. + // Committed means the normalized request and admitted declaration match the + // stored attempt. Unknown remains unresolved; this method never redispatches. + // Current actor, tenant and stored-source read authority apply before comparison. + // Source-scope refusals emit the existing failure audit; successful reads do not + // record another invocation or change the retained receipt. + // This is independent of the paired Runnable Lookup contract above. + VerifySourceOperationReceipt(ctx context.Context, in *InvokeSourceOperationRequest, opts ...grpc.CallOption) (*InvokeSourceOperationResponse, error) // ListSourceOperations reads the currently admitted declarations under source authority. ListSourceOperations(ctx context.Context, in *ListSourceOperationsRequest, opts ...grpc.CallOption) (*ListSourceOperationsResponse, error) // DeclareSourceOperations atomically replaces the source operation set, as an administrator. @@ -271,6 +281,16 @@ func (c *datasourceServiceClient) LookupInvokeSourceOperation(ctx context.Contex return out, nil } +func (c *datasourceServiceClient) VerifySourceOperationReceipt(ctx context.Context, in *InvokeSourceOperationRequest, opts ...grpc.CallOption) (*InvokeSourceOperationResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(InvokeSourceOperationResponse) + err := c.cc.Invoke(ctx, DatasourceService_VerifySourceOperationReceipt_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *datasourceServiceClient) ListSourceOperations(ctx context.Context, in *ListSourceOperationsRequest, opts ...grpc.CallOption) (*ListSourceOperationsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListSourceOperationsResponse) @@ -597,6 +617,15 @@ type DatasourceServiceServer interface { // Once an attempt exists, retained receipt evidence survives declaration // removal or replacement. Current source read authority still applies. LookupInvokeSourceOperation(context.Context, *LookupInvokeSourceOperationRequest) (*InvokeSourceOperationResponse, error) + // VerifySourceOperationReceipt conditionally reads retained output using the + // exact original Invoke request, including a nonempty declaration precondition. + // Committed means the normalized request and admitted declaration match the + // stored attempt. Unknown remains unresolved; this method never redispatches. + // Current actor, tenant and stored-source read authority apply before comparison. + // Source-scope refusals emit the existing failure audit; successful reads do not + // record another invocation or change the retained receipt. + // This is independent of the paired Runnable Lookup contract above. + VerifySourceOperationReceipt(context.Context, *InvokeSourceOperationRequest) (*InvokeSourceOperationResponse, error) // ListSourceOperations reads the currently admitted declarations under source authority. ListSourceOperations(context.Context, *ListSourceOperationsRequest) (*ListSourceOperationsResponse, error) // DeclareSourceOperations atomically replaces the source operation set, as an administrator. @@ -758,6 +787,9 @@ func (UnimplementedDatasourceServiceServer) InvokeSourceOperation(context.Contex func (UnimplementedDatasourceServiceServer) LookupInvokeSourceOperation(context.Context, *LookupInvokeSourceOperationRequest) (*InvokeSourceOperationResponse, error) { return nil, status.Error(codes.Unimplemented, "method LookupInvokeSourceOperation not implemented") } +func (UnimplementedDatasourceServiceServer) VerifySourceOperationReceipt(context.Context, *InvokeSourceOperationRequest) (*InvokeSourceOperationResponse, error) { + return nil, status.Error(codes.Unimplemented, "method VerifySourceOperationReceipt not implemented") +} func (UnimplementedDatasourceServiceServer) ListSourceOperations(context.Context, *ListSourceOperationsRequest) (*ListSourceOperationsResponse, error) { return nil, status.Error(codes.Unimplemented, "method ListSourceOperations not implemented") } @@ -941,6 +973,24 @@ func _DatasourceService_LookupInvokeSourceOperation_Handler(srv interface{}, ctx return interceptor(ctx, in, info, handler) } +func _DatasourceService_VerifySourceOperationReceipt_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(InvokeSourceOperationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DatasourceServiceServer).VerifySourceOperationReceipt(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DatasourceService_VerifySourceOperationReceipt_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DatasourceServiceServer).VerifySourceOperationReceipt(ctx, req.(*InvokeSourceOperationRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _DatasourceService_ListSourceOperations_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListSourceOperationsRequest) if err := dec(in); err != nil { @@ -1504,6 +1554,10 @@ var DatasourceService_ServiceDesc = grpc.ServiceDesc{ MethodName: "LookupInvokeSourceOperation", Handler: _DatasourceService_LookupInvokeSourceOperation_Handler, }, + { + MethodName: "VerifySourceOperationReceipt", + Handler: _DatasourceService_VerifySourceOperationReceipt_Handler, + }, { MethodName: "ListSourceOperations", Handler: _DatasourceService_ListSourceOperations_Handler, diff --git a/module/services/accounts/generated/api-contract-surfaces.json b/module/services/accounts/generated/api-contract-surfaces.json index 542eb61a1..e20dd44a1 100644 --- a/module/services/accounts/generated/api-contract-surfaces.json +++ b/module/services/accounts/generated/api-contract-surfaces.json @@ -238,7 +238,8 @@ "/saas.accounts.v1.DatasourceService/SyncSource", "/saas.accounts.v1.DatasourceService/UnbindDatasourceGroup", "/saas.accounts.v1.DatasourceService/UpdateSource", - "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain" + "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain", + "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt" ] }, { diff --git a/module/services/accounts/generated/authz-methods.json b/module/services/accounts/generated/authz-methods.json index d4274ef92..75517dc17 100644 --- a/module/services/accounts/generated/authz-methods.json +++ b/module/services/accounts/generated/authz-methods.json @@ -4117,6 +4117,55 @@ "policy_sha256": "3572d1c600161dd9cc67eadd737c10f79f7c9e2ed73140465031538f7baa3e85", "rate_limit_backend_failure_mode": "RATE_LIMIT_BACKEND_FAILURE_MODE_FAIL_OPEN" }, + { + "procedure": "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt", + "full_service": "saas.accounts.v1.DatasourceService", + "method": "VerifySourceOperationReceipt", + "input_type": "saas.accounts.v1.InvokeSourceOperationRequest", + "output_type": "saas.accounts.v1.InvokeSourceOperationResponse", + "client_streaming": false, + "server_streaming": false, + "policy": { + "exposure": "EXPOSURE_AUTHENTICATED", + "tenant": "TENANT_REQUIREMENT_ORG_MEMBER", + "permissions": [ + "datasource:read" + ], + "scopes": [ + "datasource:read" + ], + "resource_bindings": [ + { + "request_field": "effect_id", + "target": "RESOURCE_TARGET_OWNED_RESOURCE", + "lookup": "RESOURCE_LOOKUP_RESOURCE_TO_ORGANIZATION" + } + ], + "mfa": "MFA_REQUIREMENT_NONE", + "audit": { + "events": [ + "saas.datasource.operation.invoked" + ], + "emission": "AUDIT_EMISSION_FAILURE" + }, + "idempotency": "IDEMPOTENCY_REQUIREMENT_FORBIDDEN", + "rate_limit": "RATE_LIMIT_CLASS_STANDARD_READ", + "request_sensitivity": "SENSITIVITY_SECRET", + "response_sensitivity": "SENSITIVITY_SECRET", + "platform_role": "PLATFORM_ROLE_REQUIREMENT_NONE", + "authentication_factor_attempt": false, + "conditions": [], + "impersonation": "IMPERSONATION_REQUIREMENT_UNSPECIFIED" + }, + "policy_tier": "org_member", + "owner": { + "module": "saas-starter", + "service": "accounts" + }, + "source_proto": "saas/accounts/v1/datasource.proto", + "policy_sha256": "27c9767570b03f81aa5dea977fb442ce9e9221412445a159d927b843738c80a8", + "rate_limit_backend_failure_mode": "RATE_LIMIT_BACKEND_FAILURE_MODE_FAIL_OPEN" + }, { "procedure": "/saas.accounts.v1.DelegationService/DecideDelegation", "full_service": "saas.accounts.v1.DelegationService", diff --git a/module/services/accounts/generated/gateway-routes.json b/module/services/accounts/generated/gateway-routes.json index 5a558fd21..07bdacb02 100644 --- a/module/services/accounts/generated/gateway-routes.json +++ b/module/services/accounts/generated/gateway-routes.json @@ -1089,6 +1089,22 @@ "rewrite_path": "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain", "remove_after": "2026-10-11" }, + { + "protocol": "GATEWAY_PROTOCOL_CONNECT", + "method": "POST", + "path": "/customers.DatasourceService/VerifySourceOperationReceipt", + "match": "GATEWAY_MATCH_EXACT", + "procedure": "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt", + "owner": { + "module": "saas-starter", + "service": "accounts" + }, + "upstream_endpoint": "connect", + "exposure": "EXPOSURE_AUTHENTICATED", + "source": "GATEWAY_ROUTE_SOURCE_COMPATIBILITY_ALIAS", + "rewrite_path": "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt", + "remove_after": "2026-10-11" + }, { "protocol": "GATEWAY_PROTOCOL_CONNECT", "method": "POST", @@ -4945,6 +4961,22 @@ "rewrite_path": "", "remove_after": "" }, + { + "protocol": "GATEWAY_PROTOCOL_CONNECT", + "method": "POST", + "path": "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt", + "match": "GATEWAY_MATCH_EXACT", + "procedure": "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt", + "owner": { + "module": "saas-starter", + "service": "accounts" + }, + "upstream_endpoint": "connect", + "exposure": "EXPOSURE_AUTHENTICATED", + "source": "GATEWAY_ROUTE_SOURCE_DESCRIPTOR", + "rewrite_path": "", + "remove_after": "" + }, { "protocol": "GATEWAY_PROTOCOL_CONNECT", "method": "POST", @@ -8993,6 +9025,22 @@ "rewrite_path": "", "remove_after": "" }, + { + "protocol": "GATEWAY_PROTOCOL_REST", + "method": "POST", + "path": "/v1/datasource-operation-receipts/verify", + "match": "GATEWAY_MATCH_EXACT", + "procedure": "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt", + "owner": { + "module": "saas-starter", + "service": "accounts" + }, + "upstream_endpoint": "rest", + "exposure": "EXPOSURE_AUTHENTICATED", + "source": "GATEWAY_ROUTE_SOURCE_DESCRIPTOR", + "rewrite_path": "", + "remove_after": "" + }, { "protocol": "GATEWAY_PROTOCOL_REST", "method": "POST", diff --git a/module/services/accounts/generated/openapi-raw/api.swagger.json b/module/services/accounts/generated/openapi-raw/api.swagger.json index 115c508ec..9bcd53145 100644 --- a/module/services/accounts/generated/openapi-raw/api.swagger.json +++ b/module/services/accounts/generated/openapi-raw/api.swagger.json @@ -1400,6 +1400,40 @@ ] } }, + "/v1/datasource-operation-receipts/verify": { + "post": { + "summary": "VerifySourceOperationReceipt conditionally reads retained output using the\nexact original Invoke request, including a nonempty declaration precondition.\nCommitted means the normalized request and admitted declaration match the\nstored attempt. Unknown remains unresolved; this method never redispatches.\nCurrent actor, tenant and stored-source read authority apply before comparison.\nSource-scope refusals emit the existing failure audit; successful reads do not\nrecord another invocation or change the retained receipt.\nThis is independent of the paired Runnable Lookup contract above.", + "operationId": "DatasourceService_VerifySourceOperationReceipt", + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1InvokeSourceOperationResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googleRpcStatus" + } + } + }, + "parameters": [ + { + "name": "body", + "description": "InvokeSourceOperationRequest carries exactly one JSON object in input_json.\neffect_id must match x-codefly-effect-id; either location may supply it.", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1InvokeSourceOperationRequest" + } + } + ], + "tags": [ + "DatasourceService" + ] + } + }, "/v1/datasource-operation-receipts/{effectId}": { "get": { "summary": "LookupInvokeSourceOperation recovers a receipt without contacting the provider.\nNotFound \"source effect not found\" means there is no attempt marker; retrying\nthe same effect is safe. An unknown receipt never authorizes redispatch.\nOnce an attempt exists, retained receipt evidence survives declaration\nremoval or replacement. Current source read authority still applies.", @@ -9578,6 +9612,31 @@ }, "description": "Invoice — trimmed view of Stripe's invoice object. Amounts are in\nthe smallest currency unit (cents for USD/EUR); FE divides by 100." }, + "v1InvokeSourceOperationRequest": { + "type": "object", + "properties": { + "orgId": { + "type": "string" + }, + "sourceId": { + "type": "string" + }, + "operation": { + "type": "string" + }, + "inputJson": { + "type": "string" + }, + "effectId": { + "type": "string" + }, + "expectedDeclarationDigest": { + "type": "string", + "description": "Optional for legacy callers. A caller that admitted a declaration supplies\nits canonical digest; a replacement is refused before provider dispatch.\nThis precondition participates in the receipt request fingerprint." + } + }, + "description": "InvokeSourceOperationRequest carries exactly one JSON object in input_json.\neffect_id must match x-codefly-effect-id; either location may supply it." + }, "v1InvokeSourceOperationResponse": { "type": "object", "properties": { diff --git a/module/services/accounts/generated/rest-surface.json b/module/services/accounts/generated/rest-surface.json index 870cd47c0..abaeb24da 100644 --- a/module/services/accounts/generated/rest-surface.json +++ b/module/services/accounts/generated/rest-surface.json @@ -1653,6 +1653,22 @@ "rewrite_path": "", "remove_after": "" }, + { + "protocol": "GATEWAY_PROTOCOL_REST", + "method": "POST", + "path": "/v1/datasource-operation-receipts/verify", + "match": "GATEWAY_MATCH_EXACT", + "procedure": "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt", + "owner": { + "module": "saas-starter", + "service": "accounts" + }, + "upstream_endpoint": "rest", + "exposure": "EXPOSURE_AUTHENTICATED", + "source": "GATEWAY_ROUTE_SOURCE_DESCRIPTOR", + "rewrite_path": "", + "remove_after": "" + }, { "protocol": "GATEWAY_PROTOCOL_REST", "method": "POST", diff --git a/module/services/accounts/generated/service-catalog.json b/module/services/accounts/generated/service-catalog.json index 87edf3db3..10c0e5895 100644 --- a/module/services/accounts/generated/service-catalog.json +++ b/module/services/accounts/generated/service-catalog.json @@ -198,7 +198,8 @@ "/saas.accounts.v1.DatasourceService/SyncSource", "/saas.accounts.v1.DatasourceService/UnbindDatasourceGroup", "/saas.accounts.v1.DatasourceService/UpdateSource", - "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain" + "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain", + "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt" ] }, { @@ -5558,6 +5559,69 @@ "service": "accounts" } }, + { + "procedure": "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt", + "service": "DatasourceService", + "full_service": "saas.accounts.v1.DatasourceService", + "method": "VerifySourceOperationReceipt", + "input_type": "saas.accounts.v1.InvokeSourceOperationRequest", + "output_type": "saas.accounts.v1.InvokeSourceOperationResponse", + "client_streaming": false, + "server_streaming": false, + "deprecated": false, + "source_proto": "saas/accounts/v1/datasource.proto", + "protocols": [ + "PROTOCOL_GRPC", + "PROTOCOL_CONNECT", + "PROTOCOL_REST" + ], + "http_bindings": [ + { + "method": "POST", + "path": "/v1/datasource-operation-receipts/verify", + "body": "*", + "response_body": "" + } + ], + "policy": { + "exposure": "EXPOSURE_AUTHENTICATED", + "tenant": "TENANT_REQUIREMENT_ORG_MEMBER", + "permissions": [ + "datasource:read" + ], + "scopes": [ + "datasource:read" + ], + "resource_bindings": [ + { + "request_field": "effect_id", + "target": "RESOURCE_TARGET_OWNED_RESOURCE", + "lookup": "RESOURCE_LOOKUP_RESOURCE_TO_ORGANIZATION" + } + ], + "mfa": "MFA_REQUIREMENT_NONE", + "audit": { + "events": [ + "saas.datasource.operation.invoked" + ], + "emission": "AUDIT_EMISSION_FAILURE" + }, + "idempotency": "IDEMPOTENCY_REQUIREMENT_FORBIDDEN", + "rate_limit": "RATE_LIMIT_CLASS_STANDARD_READ", + "request_sensitivity": "SENSITIVITY_SECRET", + "response_sensitivity": "SENSITIVITY_SECRET", + "platform_role": "PLATFORM_ROLE_REQUIREMENT_NONE", + "authentication_factor_attempt": false, + "conditions": [], + "impersonation": "IMPERSONATION_REQUIREMENT_UNSPECIFIED" + }, + "policy_tier": "org_member", + "description": "Read a source receipt only when the original invocation and admitted declaration match.", + "owner": { + "module": "saas-starter", + "service": "accounts" + } + }, { "procedure": "/saas.accounts.v1.DelegationService/DecideDelegation", "service": "DelegationService", diff --git a/module/services/accounts/openapi/api.swagger.json b/module/services/accounts/openapi/api.swagger.json index 7450d6bd1..944ca0760 100644 --- a/module/services/accounts/openapi/api.swagger.json +++ b/module/services/accounts/openapi/api.swagger.json @@ -2757,6 +2757,31 @@ }, "type": "object" }, + "v1InvokeSourceOperationRequest": { + "description": "InvokeSourceOperationRequest carries exactly one JSON object in input_json.\neffect_id must match x-codefly-effect-id; either location may supply it.", + "properties": { + "effectId": { + "type": "string" + }, + "expectedDeclarationDigest": { + "description": "Optional for legacy callers. A caller that admitted a declaration supplies\nits canonical digest; a replacement is refused before provider dispatch.\nThis precondition participates in the receipt request fingerprint.", + "type": "string" + }, + "inputJson": { + "type": "string" + }, + "operation": { + "type": "string" + }, + "orgId": { + "type": "string" + }, + "sourceId": { + "type": "string" + } + }, + "type": "object" + }, "v1InvokeSourceOperationResponse": { "properties": { "outputJson": { @@ -7492,6 +7517,40 @@ ] } }, + "/v1/datasource-operation-receipts/verify": { + "post": { + "operationId": "DatasourceService_VerifySourceOperationReceipt", + "parameters": [ + { + "description": "InvokeSourceOperationRequest carries exactly one JSON object in input_json.\neffect_id must match x-codefly-effect-id; either location may supply it.", + "in": "body", + "name": "body", + "required": true, + "schema": { + "$ref": "#/definitions/v1InvokeSourceOperationRequest" + } + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/v1InvokeSourceOperationResponse" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "#/definitions/googleRpcStatus" + } + } + }, + "summary": "VerifySourceOperationReceipt conditionally reads retained output using the\nexact original Invoke request, including a nonempty declaration precondition.\nCommitted means the normalized request and admitted declaration match the\nstored attempt. Unknown remains unresolved; this method never redispatches.\nCurrent actor, tenant and stored-source read authority apply before comparison.\nSource-scope refusals emit the existing failure audit; successful reads do not\nrecord another invocation or change the retained receipt.\nThis is independent of the paired Runnable Lookup contract above.", + "tags": [ + "DatasourceService" + ] + } + }, "/v1/datasource-operation-receipts/{effectId}": { "get": { "operationId": "DatasourceService_LookupInvokeSourceOperation", diff --git a/module/services/accounts/proto/saas/accounts/v1/datasource.proto b/module/services/accounts/proto/saas/accounts/v1/datasource.proto index c95696bd1..9d81a63ab 100644 --- a/module/services/accounts/proto/saas/accounts/v1/datasource.proto +++ b/module/services/accounts/proto/saas/accounts/v1/datasource.proto @@ -1662,6 +1662,42 @@ service DatasourceService { }; } + // VerifySourceOperationReceipt conditionally reads retained output using the + // exact original Invoke request, including a nonempty declaration precondition. + // Committed means the normalized request and admitted declaration match the + // stored attempt. Unknown remains unresolved; this method never redispatches. + // Current actor, tenant and stored-source read authority apply before comparison. + // Source-scope refusals emit the existing failure audit; successful reads do not + // record another invocation or change the retained receipt. + // This is independent of the paired Runnable Lookup contract above. + rpc VerifySourceOperationReceipt(InvokeSourceOperationRequest) returns (InvokeSourceOperationResponse) { + option (google.api.http) = { + post: "/v1/datasource-operation-receipts/verify" + body: "*" + }; + option (saas.policy.v1.method_policy) = { + exposure: EXPOSURE_AUTHENTICATED + tenant: TENANT_REQUIREMENT_ORG_MEMBER + permissions: "datasource:read" + scopes: "datasource:read" + resource_bindings: { + request_field: "effect_id" + target: RESOURCE_TARGET_OWNED_RESOURCE + lookup: RESOURCE_LOOKUP_RESOURCE_TO_ORGANIZATION + } + mfa: MFA_REQUIREMENT_NONE + audit: { + events: "saas.datasource.operation.invoked" + emission: AUDIT_EMISSION_FAILURE + } + idempotency: IDEMPOTENCY_REQUIREMENT_FORBIDDEN + rate_limit: RATE_LIMIT_CLASS_STANDARD_READ + request_sensitivity: SENSITIVITY_SECRET + response_sensitivity: SENSITIVITY_SECRET + platform_role: PLATFORM_ROLE_REQUIREMENT_NONE + }; + } + // ListSourceOperations reads the currently admitted declarations under source authority. rpc ListSourceOperations(ListSourceOperationsRequest) returns (ListSourceOperationsResponse) { option (google.api.http) = {get: "/v1/organizations/{org_id}/datasources/{source_id}/operations"}; diff --git a/module/services/auth-gateway/code/authz_catalog_gen.go b/module/services/auth-gateway/code/authz_catalog_gen.go index 0643fdfda..217cedb42 100644 --- a/module/services/auth-gateway/code/authz_catalog_gen.go +++ b/module/services/auth-gateway/code/authz_catalog_gen.go @@ -109,6 +109,7 @@ var generatedAuthorizationByProcedure = map[string]generatedAuthorizationMetadat "/saas.accounts.v1.DatasourceService/UnbindDatasourceGroup": {exposure: edgeExposureAuthenticated, rateLimitClass: edgeRateLimitClassStandardWrite, rateLimitBackendFailClosed: false, authenticationFactorAttempt: false, policySHA256: "d658d313d1ed64a0e5493f089c8c2339c8e4c4bc714e3a10c4cd2ee963c2d3e7"}, "/saas.accounts.v1.DatasourceService/UpdateSource": {exposure: edgeExposureAuthenticated, rateLimitClass: edgeRateLimitClassStandardWrite, rateLimitBackendFailClosed: false, authenticationFactorAttempt: false, policySHA256: "8f18fbf423e4a4f0afa902be617d8b960ec24a684774367482aead685b1b19b0"}, "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain": {exposure: edgeExposureAuthenticated, rateLimitClass: edgeRateLimitClassStandardWrite, rateLimitBackendFailClosed: false, authenticationFactorAttempt: false, policySHA256: "3572d1c600161dd9cc67eadd737c10f79f7c9e2ed73140465031538f7baa3e85"}, + "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt": {exposure: edgeExposureAuthenticated, rateLimitClass: edgeRateLimitClassStandardRead, rateLimitBackendFailClosed: false, authenticationFactorAttempt: false, policySHA256: "27c9767570b03f81aa5dea977fb442ce9e9221412445a159d927b843738c80a8"}, "/saas.accounts.v1.DelegationService/DecideDelegation": {exposure: edgeExposureAuthenticated, rateLimitClass: edgeRateLimitClassStandardRead, rateLimitBackendFailClosed: false, authenticationFactorAttempt: false, policySHA256: "09931634771ddd7607ac12157e0f55b08894234bf81b188659590ba202f4d486"}, "/saas.accounts.v1.DelegationService/ListPendingDelegations": {exposure: edgeExposureAuthenticated, rateLimitClass: edgeRateLimitClassStandardRead, rateLimitBackendFailClosed: false, authenticationFactorAttempt: false, policySHA256: "ad07a0509490d86d9bb64cb28e5db5ef578575d2cbab8ee86d279ffe1392cca8"}, "/saas.accounts.v1.DelegationService/RequestDelegation": {exposure: edgeExposureAuthenticated, rateLimitClass: edgeRateLimitClassStandardWrite, rateLimitBackendFailClosed: false, authenticationFactorAttempt: false, policySHA256: "9f9023065c47c9a66cfe1d5893b78e28d36a08f377b078451d51eda61910d199"}, @@ -352,6 +353,7 @@ var generatedRESTProcedureByRoute = map[string]string{ "POST /v1/organizations/{org_id}/datasources/{source_id}/operations/{operation}:invoke": "/saas.accounts.v1.DatasourceService/InvokeSourceOperation", "GET /v1/organizations/{org_id}/datasources/{source_id}/operations": "/saas.accounts.v1.DatasourceService/ListSourceOperations", "GET /v1/datasource-operation-receipts/{effect_id}": "/saas.accounts.v1.DatasourceService/LookupInvokeSourceOperation", + "POST /v1/datasource-operation-receipts/verify": "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt", "POST /v1/delegations/{id}:decide": "/saas.accounts.v1.DelegationService/DecideDelegation", "GET /v1/delegations:pending": "/saas.accounts.v1.DelegationService/ListPendingDelegations", "POST /v1/delegations": "/saas.accounts.v1.DelegationService/RequestDelegation", diff --git a/module/services/auth-gateway/code/routing_catalog_gen.go b/module/services/auth-gateway/code/routing_catalog_gen.go index 704e2aa90..9d82c81b8 100644 --- a/module/services/auth-gateway/code/routing_catalog_gen.go +++ b/module/services/auth-gateway/code/routing_catalog_gen.go @@ -74,6 +74,7 @@ func generatedCatalogConnectRoutes() []*RouteEntry { {Service: "accounts_connect", Method: "POST", Path: "/customers.DatasourceService/UnbindDatasourceGroup", UpstreamPath: "/saas.accounts.v1.DatasourceService/UnbindDatasourceGroup", Procedure: "/saas.accounts.v1.DatasourceService/UnbindDatasourceGroup"}, {Service: "accounts_connect", Method: "POST", Path: "/customers.DatasourceService/UpdateSource", UpstreamPath: "/saas.accounts.v1.DatasourceService/UpdateSource", Procedure: "/saas.accounts.v1.DatasourceService/UpdateSource"}, {Service: "accounts_connect", Method: "POST", Path: "/customers.DatasourceService/VerifyDatasourceDomain", UpstreamPath: "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain", Procedure: "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain"}, + {Service: "accounts_connect", Method: "POST", Path: "/customers.DatasourceService/VerifySourceOperationReceipt", UpstreamPath: "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt", Procedure: "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt"}, {Service: "accounts_connect", Method: "POST", Path: "/customers.DelegationService/DecideDelegation", UpstreamPath: "/saas.accounts.v1.DelegationService/DecideDelegation", Procedure: "/saas.accounts.v1.DelegationService/DecideDelegation"}, {Service: "accounts_connect", Method: "POST", Path: "/customers.DelegationService/ListPendingDelegations", UpstreamPath: "/saas.accounts.v1.DelegationService/ListPendingDelegations", Procedure: "/saas.accounts.v1.DelegationService/ListPendingDelegations"}, {Service: "accounts_connect", Method: "POST", Path: "/customers.DelegationService/RequestDelegation", UpstreamPath: "/saas.accounts.v1.DelegationService/RequestDelegation", Procedure: "/saas.accounts.v1.DelegationService/RequestDelegation"}, @@ -315,6 +316,7 @@ func generatedCatalogConnectRoutes() []*RouteEntry { {Service: "accounts_connect", Method: "POST", Path: "/saas.accounts.v1.DatasourceService/UnbindDatasourceGroup", UpstreamPath: "", Procedure: "/saas.accounts.v1.DatasourceService/UnbindDatasourceGroup"}, {Service: "accounts_connect", Method: "POST", Path: "/saas.accounts.v1.DatasourceService/UpdateSource", UpstreamPath: "", Procedure: "/saas.accounts.v1.DatasourceService/UpdateSource"}, {Service: "accounts_connect", Method: "POST", Path: "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain", UpstreamPath: "", Procedure: "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain"}, + {Service: "accounts_connect", Method: "POST", Path: "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt", UpstreamPath: "", Procedure: "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt"}, {Service: "accounts_connect", Method: "POST", Path: "/saas.accounts.v1.DelegationService/DecideDelegation", UpstreamPath: "", Procedure: "/saas.accounts.v1.DelegationService/DecideDelegation"}, {Service: "accounts_connect", Method: "POST", Path: "/saas.accounts.v1.DelegationService/ListPendingDelegations", UpstreamPath: "", Procedure: "/saas.accounts.v1.DelegationService/ListPendingDelegations"}, {Service: "accounts_connect", Method: "POST", Path: "/saas.accounts.v1.DelegationService/RequestDelegation", UpstreamPath: "", Procedure: "/saas.accounts.v1.DelegationService/RequestDelegation"}, diff --git a/module/services/auth-gateway/code/routing_rest_catalog_gen.go b/module/services/auth-gateway/code/routing_rest_catalog_gen.go index 7ff0c1f1f..feff1ce63 100644 --- a/module/services/auth-gateway/code/routing_rest_catalog_gen.go +++ b/module/services/auth-gateway/code/routing_rest_catalog_gen.go @@ -107,6 +107,7 @@ func generatedCatalogRESTRoutes() []*RouteEntry { {Service: "accounts", Method: "POST", Path: "/v1/auth/token", Procedure: "/saas.accounts.v1.AuthService/ExchangeClientToken"}, {Service: "accounts", Method: "POST", Path: "/v1/billing/connect/portal", Procedure: "/saas.accounts.v1.BillingService/OpenPortal"}, {Service: "accounts", Method: "POST", Path: "/v1/consent/terms", Procedure: "/saas.accounts.v1.ConsentService/AcceptTerms"}, + {Service: "accounts", Method: "POST", Path: "/v1/datasource-operation-receipts/verify", Procedure: "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt"}, {Service: "accounts", Method: "POST", Path: "/v1/delegations", Procedure: "/saas.accounts.v1.DelegationService/RequestDelegation"}, {Service: "accounts", Method: "POST", Path: "/v1/delegations/{id}:decide", Procedure: "/saas.accounts.v1.DelegationService/DecideDelegation"}, {Service: "accounts", Method: "POST", Path: "/v1/gdpr/delete", Procedure: "/saas.accounts.v1.GDPRService/RequestDeletion"}, diff --git a/module/services/frontend/code/package-lock.json b/module/services/frontend/code/package-lock.json index f5679667a..14848ffbb 100644 --- a/module/services/frontend/code/package-lock.json +++ b/module/services/frontend/code/package-lock.json @@ -15,7 +15,7 @@ "@bufbuild/protobuf": "^2.11.0", "@codefly-dev/saas-plugin-contract": "2.4.4", "@codefly-dev/saas-plugin-react": "0.4.5", - "@codefly-dev/saas-sdk": "0.3.42", + "@codefly-dev/saas-sdk": "0.3.43", "@codefly-dev/saas-ui": "0.13.0", "@codefly-dev/ui": "0.13.0", "@codefly/saas-plugin-manifest": "0.1.2", @@ -15742,7 +15742,7 @@ }, "packages/saas-sdk": { "name": "@codefly-dev/saas-sdk", - "version": "0.3.42", + "version": "0.3.43", "license": "Apache-2.0", "dependencies": { "@bufbuild/protobuf": "^2.11.0", diff --git a/module/services/frontend/code/package.json b/module/services/frontend/code/package.json index 815db3fbb..5ea30bef4 100644 --- a/module/services/frontend/code/package.json +++ b/module/services/frontend/code/package.json @@ -39,7 +39,7 @@ "@bufbuild/protobuf": "^2.11.0", "@codefly-dev/saas-plugin-contract": "2.4.4", "@codefly-dev/saas-plugin-react": "0.4.5", - "@codefly-dev/saas-sdk": "0.3.42", + "@codefly-dev/saas-sdk": "0.3.43", "@codefly-dev/saas-ui": "0.13.0", "@codefly-dev/ui": "0.13.0", "@codefly/saas-plugin-manifest": "0.1.2", diff --git a/module/services/frontend/code/packages/saas-sdk/generated/contract/catalog.codefly.json b/module/services/frontend/code/packages/saas-sdk/generated/contract/catalog.codefly.json index 68c3d388e..6cbffdf2c 100644 --- a/module/services/frontend/code/packages/saas-sdk/generated/contract/catalog.codefly.json +++ b/module/services/frontend/code/packages/saas-sdk/generated/contract/catalog.codefly.json @@ -10,7 +10,7 @@ "kind": "protobuf", "package": "", "path": "contract.binpb", - "digest": "sha256:da3be80f4ed222b07d24e2b23d5eb797a36b1334fef560b24aeecf2850ac48d6", + "digest": "sha256:4d0ed4cb2d252595ca1c0cd5a8ec61d8aa015948f493701a737aaa76d24304a4", "services": [ { "name": "AccessibleScopeService", @@ -66,7 +66,8 @@ "/saas.accounts.v1.DatasourceService/SyncSource", "/saas.accounts.v1.DatasourceService/UnbindDatasourceGroup", "/saas.accounts.v1.DatasourceService/UpdateSource", - "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain" + "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain", + "/saas.accounts.v1.DatasourceService/VerifySourceOperationReceipt" ] }, { diff --git a/module/services/frontend/code/packages/saas-sdk/generated/contract/contract.binpb b/module/services/frontend/code/packages/saas-sdk/generated/contract/contract.binpb index 4d1a16547..86ebc6a1e 100644 Binary files a/module/services/frontend/code/packages/saas-sdk/generated/contract/contract.binpb and b/module/services/frontend/code/packages/saas-sdk/generated/contract/contract.binpb differ diff --git a/module/services/frontend/code/packages/saas-sdk/generated/library.codefly.yaml b/module/services/frontend/code/packages/saas-sdk/generated/library.codefly.yaml index c71813471..c3a871623 100644 --- a/module/services/frontend/code/packages/saas-sdk/generated/library.codefly.yaml +++ b/module/services/frontend/code/packages/saas-sdk/generated/library.codefly.yaml @@ -12,7 +12,7 @@ sources: module: saas-starter service: accounts endpoint: connect - contract-digest: sha256:606e52170c2e55688ce163eb1d54c347f0d19ac40915d7fb6252f2034b787c0e + contract-digest: sha256:9aaed4fe30672e6c11675034ae204cec14ecbb299d68a3898b65780a88f80657 services: - AccessibleScopeService - AuditService diff --git a/module/services/frontend/code/packages/saas-sdk/generated/typescript/src/gen/saas/accounts/v1/datasource_pb.ts b/module/services/frontend/code/packages/saas-sdk/generated/typescript/src/gen/saas/accounts/v1/datasource_pb.ts index 224ac5185..e737e403f 100644 --- a/module/services/frontend/code/packages/saas-sdk/generated/typescript/src/gen/saas/accounts/v1/datasource_pb.ts +++ b/module/services/frontend/code/packages/saas-sdk/generated/typescript/src/gen/saas/accounts/v1/datasource_pb.ts @@ -18,7 +18,7 @@ import type { JsonObject, Message } from "@bufbuild/protobuf"; * Describes the file saas/accounts/v1/datasource.proto. */ export const file_saas_accounts_v1_datasource: GenFile = /*@__PURE__*/ - fileDesc("CiFzYWFzL2FjY291bnRzL3YxL2RhdGFzb3VyY2UucHJvdG8SEHNhYXMuYWNjb3VudHMudjEiXgoWR2l0SHViRGF0YXNvdXJjZUNvbmZpZxIMCgRyZXBvGAEgASgJEg0KBXBhdGhzGAIgAygJEg4KBmJyYW5jaBgDIAEoCRIXCg9maWxlX2V4dGVuc2lvbnMYBCADKAki9AEKD0FwaU9BdXRoMkNvbmZpZxI2CgVncmFudBgEIAEoDjInLnNhYXMuYWNjb3VudHMudjEuQXBpT0F1dGgyQ29uZmlnLkdyYW50EhEKCXRva2VuX3VybBgBIAEoCRIRCgljbGllbnRfaWQYAiABKAkSDgoGc2NvcGVzGAMgAygJInMKBUdyYW50EhUKEUdSQU5UX1VOU1BFQ0lGSUVEEAASFwoTR1JBTlRfUkVGUkVTSF9UT0tFThABEhwKGEdSQU5UX0NMSUVOVF9DUkVERU5USUFMUxACEhwKGEdSQU5UX0FVVEhPUklaQVRJT05fQ09ERRADIuoBChNBcGlEYXRhc291cmNlQ29uZmlnEhAKCGJhc2VfdXJsGAEgASgJEhUKDXJlc291cmNlX3BhdGgYAiABKAkSPAoPY3JlZGVudGlhbF9raW5kGAMgASgOMiMuc2Fhcy5hY2NvdW50cy52MS5BcGlDcmVkZW50aWFsS2luZBIZChFjcmVkZW50aWFsX2hlYWRlchgEIAEoCRIeChZjcmVkZW50aWFsX3F1ZXJ5X3BhcmFtGAUgASgJEjEKBm9hdXRoMhgGIAEoCzIhLnNhYXMuYWNjb3VudHMudjEuQXBpT0F1dGgyQ29uZmlnIkEKF0NyYXdsZXJEYXRhc291cmNlQ29uZmlnEhMKC3NpdGVtYXBfdXJsGAEgASgJEhEKCW1heF9wYWdlcxgCIAEoDSKrAgoWVXBsb2FkRGF0YXNvdXJjZUNvbmZpZxIQCghlbmRwb2ludBgBIAEoCRIOCgZyZWdpb24YAiABKAkSDgoGYnVja2V0GAMgASgJEg4KBnByZWZpeBgEIAEoCRIVCg1hY2Nlc3Nfa2V5X2lkGAUgASgJEhMKC21heF9vYmplY3RzGAYgASgNEisKEGluY2x1ZGVfcGF0dGVybnMYByADKAlCEbpIDpIBCxAQIgdyBRABGIACEisKEGV4Y2x1ZGVfcGF0dGVybnMYCCADKAlCEbpIDpIBCxAQIgdyBRABGIACEkkKD2ZpbGVfZXh0ZW5zaW9ucxgJIAMoCUIwukgtkgEqECAiJnIkEAIYITIeXlwuW0EtWmEtejAtOV1bQS1aYS16MC05Ll8tXSokIoMHCgpEYXRhc291cmNlEgoKAmlkGAEgASgJEg4KBm9yZ19pZBgCIAEoCRI2Cghwcm92aWRlchgDIAEoDjIkLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVByb3ZpZGVyEjgKBmdpdGh1YhgFIAEoCzIoLnNhYXMuYWNjb3VudHMudjEuR2l0SHViRGF0YXNvdXJjZUNvbmZpZxIyCgZzdGF0dXMYBiABKA4yIi5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VTdGF0dXMSGgoSd2ViaG9va19jb25maWd1cmVkGAcgASgIEi4KCmNyZWF0ZWRfYXQYCCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjIKDmxhc3Rfc3luY2VkX2F0GAogASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIyCgNhcGkYCyABKAsyJS5zYWFzLmFjY291bnRzLnYxLkFwaURhdGFzb3VyY2VDb25maWcSOgoHY3Jhd2xlchgMIAEoCzIpLnNhYXMuYWNjb3VudHMudjEuQ3Jhd2xlckRhdGFzb3VyY2VDb25maWcSOAoGdXBsb2FkGA0gASgLMiguc2Fhcy5hY2NvdW50cy52MS5VcGxvYWREYXRhc291cmNlQ29uZmlnEhgKEGJvdW5kYXJ5X25vZGVfaWQYDiABKAkSNAoQbGFzdF9pbmdlc3RlZF9hdBgPIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASHAoUbGFzdF9pbmdlc3RlZF9jb21taXQYECABKAkSFQoNc3RhdHVzX3JlYXNvbhgRIAEoCRIWCg5ib3VuZGFyeV9sYWJlbBgSIAEoCRISCgpjb25mb3JtYW50GBMgASgIEhcKD2NvbmZvcm1hbmNlX2dhcBgUIAEoCRI/Cg1saXZlX2RlbGl2ZXJ5GBUgASgOMiguc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlTGl2ZURlbGl2ZXJ5EjUKEnJlY29uY2lsZV9pbnRlcnZhbBgWIAEoCzIZLmdvb2dsZS5wcm90b2J1Zi5EdXJhdGlvbkoECAQQBVIRdGFyZ2V0X2NvbGxlY3Rpb24isgMKFkFkZEdpdEh1YlNvdXJjZVJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARI7CgRyZXBvGAIgASgJQi26SCpyKBADGP8BMiFeW0EtWmEtejAtOV8uLV0rL1tBLVphLXowLTlfLi1dKyQSIAoFcGF0aHMYAyADKAlCEbpIDpIBCxBAIgdyBRABGIAEEhgKBmJyYW5jaBgEIAEoCUIIukgFcgMY/wESJAoQYm91bmRhcnlfbm9kZV9pZBgIIAEoCUIIukgFcgOwAQFIABImChBjb2xsZWN0aW9uX2xhYmVsGAkgASgJQgq6SAdyBRABGP8BSAASHgoMYWNjZXNzX3Rva2VuGAYgASgJQgi6SAVyAxiACBIgCg53ZWJob29rX3NlY3JldBgHIAEoCUIIukgFcgMYgAgSSQoPZmlsZV9leHRlbnNpb25zGAogAygJQjC6SC2SASoQICImciQQAhghMh5eXC5bQS1aYS16MC05XVtBLVphLXowLTkuXy1dKiRCEQoIYm91bmRhcnkSBbpIAggBSgQIBRAGUhF0YXJnZXRfY29sbGVjdGlvbiJLChdBZGRHaXRIdWJTb3VyY2VSZXNwb25zZRIwCgpkYXRhc291cmNlGAEgASgLMhwuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlIsgEChBBZGRTb3VyY2VSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESQgoIcHJvdmlkZXIYAiABKA4yJC5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VQcm92aWRlckIKukgHggEEEAEgABI6CgZnaXRodWIYBCABKAsyKC5zYWFzLmFjY291bnRzLnYxLkdpdEh1YkRhdGFzb3VyY2VDb25maWdIABI0CgNhcGkYBSABKAsyJS5zYWFzLmFjY291bnRzLnYxLkFwaURhdGFzb3VyY2VDb25maWdIABI8CgdjcmF3bGVyGAggASgLMikuc2Fhcy5hY2NvdW50cy52MS5DcmF3bGVyRGF0YXNvdXJjZUNvbmZpZ0gAEjoKBnVwbG9hZBgJIAEoCzIoLnNhYXMuYWNjb3VudHMudjEuVXBsb2FkRGF0YXNvdXJjZUNvbmZpZ0gAEiQKEGJvdW5kYXJ5X25vZGVfaWQYDCABKAlCCLpIBXIDsAEBSAESJgoQY29sbGVjdGlvbl9sYWJlbBgNIAEoCUIKukgHcgUQARj/AUgBEhwKCmNyZWRlbnRpYWwYBiABKAlCCLpIBXIDGIAgEiAKDndlYmhvb2tfc2VjcmV0GAcgASgJQgi6SAVyAxiACBImChRvYXV0aDJfY2xpZW50X3NlY3JldBgKIAEoCUIIukgFcgMYgAhCCAoGY29uZmlnQhEKCGJvdW5kYXJ5EgW6SAIIAUoECAMQBFIRdGFyZ2V0X2NvbGxlY3Rpb24iRQoRQWRkU291cmNlUmVzcG9uc2USMAoKZGF0YXNvdXJjZRgBIAEoCzIcLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZSLkAgoTVXBkYXRlU291cmNlUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhQKAmlkGAIgASgJQgi6SAVyA7ABARIYCgZicmFuY2gYAyABKAlCCLpIBXIDGP8BEiAKBXBhdGhzGAQgAygJQhG6SA6SAQsQQCIHcgUQARiABBJJCg9maWxlX2V4dGVuc2lvbnMYBSADKAlCMLpILZIBKhAgIiZyJBACGCEyHl5cLltBLVphLXowLTldW0EtWmEtejAtOS5fLV0qJBI8CghleHBlY3RlZBgGIAEoCzIiLnNhYXMuYWNjb3VudHMudjEuU291cmNlRmV0Y2hTY29wZUIGukgDyAEBEisKEGluY2x1ZGVfcGF0dGVybnMYByADKAlCEbpIDpIBCxAQIgdyBRABGIACEisKEGV4Y2x1ZGVfcGF0dGVybnMYCCADKAlCEbpIDpIBCxAQIgdyBRABGIACIs8BChBTb3VyY2VGZXRjaFNjb3BlEhgKBmJyYW5jaBgBIAEoCUIIukgFcgMY/wESIAoFcGF0aHMYAiADKAlCEbpIDpIBCxBAIgdyBRABGIAEEikKD2ZpbGVfZXh0ZW5zaW9ucxgDIAMoCUIQukgNkgEKECAiBnIEEAEYIRIpChBpbmNsdWRlX3BhdHRlcm5zGAQgAygJQg+6SAySAQkQECIFcgMYgAISKQoQZXhjbHVkZV9wYXR0ZXJucxgFIAMoCUIPukgMkgEJEBAiBXIDGIACIlgKFFVwZGF0ZVNvdXJjZVJlc3BvbnNlEjAKCmRhdGFzb3VyY2UYASABKAsyHC5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2USDgoGam9iX2lkGAIgASgJIloKFURhdGFzb3VyY2VDb25maWdGaWVsZBILCgNrZXkYASABKAkSFAoMZGlzcGxheV9uYW1lGAIgASgJEgwKBGhlbHAYAyABKAkSEAoIcmVxdWlyZWQYBCABKAgixAEKGURhdGFzb3VyY2VDb25uZWN0b3JCdWRnZXQSGgoSbWF4X2l0ZW1zX3Blcl9jYWxsGAEgASgNEhoKEm1heF9ieXRlc19wZXJfY2FsbBgCIAEoAxIWCg5tYXhfaXRlbV9ieXRlcxgDIAEoAxIdChVvcGVyYXRpb25zX3Blcl93aW5kb3cYBCABKA0SFgoOd2luZG93X3NlY29uZHMYBSABKAMSIAoYYmFja2dyb3VuZF9zaGFyZV9wZXJjZW50GAYgASgNIqoGChxEYXRhc291cmNlUHJvdmlkZXJEZXNjcmlwdG9yEjYKCHByb3ZpZGVyGAEgASgOMiQuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlUHJvdmlkZXISFAoMZGlzcGxheV9uYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEj4KDWNvbmZpZ19maWVsZHMYBCADKAsyJy5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VDb25maWdGaWVsZBIYChBzdXBwb3J0c193ZWJob29rGAUgASgIEkcKGnN1cHBvcnRlZF9jcmVkZW50aWFsX2tpbmRzGAYgAygOMiMuc2Fhcy5hY2NvdW50cy52MS5BcGlDcmVkZW50aWFsS2luZBIRCgljb25uZWN0b3IYByABKAkSQQoJaW50ZXJmYWNlGAggASgOMi4uc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlQ29ubmVjdG9ySW50ZXJmYWNlEkQKEGNyZWRlbnRpYWxfbW9kZXMYCSADKA4yKi5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VDcmVkZW50aWFsTW9kZRI/Cg1yZWFkZXJzX21vZGVsGAogASgOMiguc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlUmVhZGVyc01vZGVsEjsKBmJ1ZGdldBgLIAEoCzIrLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZUNvbm5lY3RvckJ1ZGdldBISCgpjb25mb3JtYW50GAwgASgIEhcKD2NvbmZvcm1hbmNlX2dhcBgNIAEoCRIbChNhY2NlcHRzX25ld19zb3VyY2VzGA4gASgIEiAKGGxpdmVfZGVsaXZlcnlfY29uZmlndXJlZBgPIAEoCBIaChJhY2NlcHRzX29wZXJhdGlvbnMYECABKAgSFgoOb3BlcmF0aW9uc19nYXAYESABKAkSGwoTb2F1dGhfYXV0aG9yaXplX3VybBgSIAEoCRIXCg9vYXV0aF90b2tlbl91cmwYEyABKAkSFAoMb2F1dGhfc2NvcGVzGBQgAygJIh0KG0dldERhdGFzb3VyY2VDYXRhbG9nUmVxdWVzdCJhChxHZXREYXRhc291cmNlQ2F0YWxvZ1Jlc3BvbnNlEkEKCXByb3ZpZGVycxgBIAMoCzIuLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVByb3ZpZGVyRGVzY3JpcHRvciIuChJMaXN0U291cmNlc1JlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABASJIChNMaXN0U291cmNlc1Jlc3BvbnNlEjEKC2RhdGFzb3VyY2VzGAEgAygLMhwuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlIkIKEEdldFNvdXJjZVJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIUCgJpZBgCIAEoCUIIukgFcgOwAQEiRQoRR2V0U291cmNlUmVzcG9uc2USMAoKZGF0YXNvdXJjZRgBIAEoCzIcLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZSJ2ChFTeW5jU291cmNlUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhQKAmlkGAIgASgJQgi6SAVyA7ABARIeCgxhY2Nlc3NfdG9rZW4YAyABKAlCCLpIBXIDGIAgEhEKCXJlY29ubmVjdBgEIAEoCCIkChJTeW5jU291cmNlUmVzcG9uc2USDgoGam9iX2lkGAEgASgJImoKFEdldFNvdXJjZVN5bmNSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGwoJc291cmNlX2lkGAIgASgJQgi6SAVyA7ABARIbCgZqb2JfaWQYAyABKAlCC7pICNgBAXIDsAEBIpUBChJTb3VyY2VTeW5jRGVsaXZlcnkSGAoGam9iX2lkGAEgASgJQgi6SAVyA7ABARItCgVzdGF0ZRgCIAEoDjIWLnNhYXMuam9icy52MS5Kb2JTdGF0ZUIGukgDyAEBEjYKCWV4ZWN1dGlvbhgDIAEoCzIjLnNhYXMuam9icy52MS5Kb2JFeGVjdXRpb25SZWZlcmVuY2UiiwEKEVNvdXJjZVN5bmNDaGFuZ2VzEg0KBWZpbGVzGAEgASgNEg0KBWFkZGVkGAIgASgNEhAKCG1vZGlmaWVkGAMgASgNEg8KB2RlbGV0ZWQYBCABKA0SEwoLc3BsaXRfa25vd24YBSABKAgSEAoIc25hcHNob3QYBiABKAgSDgoGY29tbWl0GAcgASgJIq0BChFTb3VyY2VTeW5jRmFpbHVyZRI5CgZyZWFzb24YASABKA4yKS5zYWFzLmFjY291bnRzLnYxLlNvdXJjZVN5bmNGYWlsdXJlUmVhc29uEgwKBGNvZGUYAiABKAkSDwoHbWVzc2FnZRgDIAEoCRIQCghyZXRyeWluZxgEIAEoCBIsCghyZXRyeV9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAihAQKElNvdXJjZVN5bmNQcm9ncmVzcxIwCgVwaGFzZRgBIAEoDjIhLnNhYXMuYWNjb3VudHMudjEuU291cmNlU3luY1BoYXNlEjQKB3RyaWdnZXIYAiABKA4yIy5zYWFzLmFjY291bnRzLnYxLlNvdXJjZVN5bmNUcmlnZ2VyEi0KCXF1ZXVlZF9hdBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLwoLZmV0Y2hpbmdfYXQYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi8KC2NvbXBpbGVkX2F0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIxCg1oYW5kZWRfb2ZmX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIvCgtmaW5pc2hlZF9hdBgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASNAoHY2hhbmdlcxgIIAEoCzIjLnNhYXMuYWNjb3VudHMudjEuU291cmNlU3luY0NoYW5nZXMSNAoHZmFpbHVyZRgJIAEoCzIjLnNhYXMuYWNjb3VudHMudjEuU291cmNlU3luY0ZhaWx1cmUSDwoHYXR0ZW1wdBgKIAEoDRIUCgxtYXhfYXR0ZW1wdHMYCyABKA0i0gEKFUdldFNvdXJjZVN5bmNSZXNwb25zZRIYCgZqb2JfaWQYASABKAlCCLpIBXIDsAEBEi0KBXN0YXRlGAIgASgOMhYuc2Fhcy5qb2JzLnYxLkpvYlN0YXRlQga6SAPIAQESOAoKZGVsaXZlcmllcxgDIAMoCzIkLnNhYXMuYWNjb3VudHMudjEuU291cmNlU3luY0RlbGl2ZXJ5EjYKCHByb2dyZXNzGAQgASgLMiQuc2Fhcy5hY2NvdW50cy52MS5Tb3VyY2VTeW5jUHJvZ3Jlc3MiRQoTRGVsZXRlU291cmNlUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhQKAmlkGAIgASgJQgi6SAVyA7ABASIWChREZWxldGVTb3VyY2VSZXNwb25zZSJWChNHaXRIdWJBcHBSZXBvc2l0b3J5EgwKBHJlcG8YASABKAkSFgoOZGVmYXVsdF9icmFuY2gYAiABKAkSGQoRYWxyZWFkeV9jb25uZWN0ZWQYAyABKAgiNgoaQmVnaW5HaXRIdWJBcHBTZXR1cFJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABASJxChtCZWdpbkdpdEh1YkFwcFNldHVwUmVzcG9uc2USEwoLaW5zdGFsbF91cmwYASABKAkSDQoFc3RhdGUYAiABKAkSLgoKZXhwaXJlc19hdBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAinAEKHUNvbXBsZXRlR2l0SHViQXBwU2V0dXBSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGQoFc3RhdGUYAiABKAlCCrpIB3IFEAEY/wESLAoPaW5zdGFsbGF0aW9uX2lkGAMgASgJQhO6SBByDhABGCAyCF5bMC05XSskEhgKBGNvZGUYBCABKAlCCrpIB3IFEAEY/wEidgoeQ29tcGxldGVHaXRIdWJBcHBTZXR1cFJlc3BvbnNlEhcKD2luc3RhbGxhdGlvbl9pZBgBIAEoCRI7CgxyZXBvc2l0b3JpZXMYAiADKAsyJS5zYWFzLmFjY291bnRzLnYxLkdpdEh1YkFwcFJlcG9zaXRvcnkiUQofTWlncmF0ZUdpdEh1YlNvdXJjZVRvQXBwUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhQKAmlkGAIgASgJQgi6SAVyA7ABASJUCiBNaWdyYXRlR2l0SHViU291cmNlVG9BcHBSZXNwb25zZRIwCgpkYXRhc291cmNlGAEgASgLMhwuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlIsQBChVEYXRhc291cmNlQWNjb3VudExpbmsSCgoCaWQYASABKAkSDgoGb3JnX2lkGAIgASgJEg8KB3VzZXJfaWQYAyABKAkSEQoJY29ubmVjdG9yGAQgASgJEhsKE3Byb3ZpZGVyX2FjY291bnRfaWQYBSABKAkSHgoWcHJvdmlkZXJfYWNjb3VudF9sb2dpbhgGIAEoCRIuCgpjcmVhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCK3AQoWRGF0YXNvdXJjZUdyb3VwQmluZGluZxIKCgJpZBgBIAEoCRIOCgZvcmdfaWQYAiABKAkSEQoJY29ubmVjdG9yGAMgASgJEhkKEXByb3ZpZGVyX2dyb3VwX2lkGAQgASgJEg8KB3RlYW1faWQYBSABKAkSEgoKY3JlYXRlZF9ieRgGIAEoCRIuCgpjcmVhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKUAgoYRGF0YXNvdXJjZVZlcmlmaWVkRG9tYWluEgoKAmlkGAEgASgJEg4KBm9yZ19pZBgCIAEoCRIOCgZkb21haW4YAyABKAkSOAoGc3RhdHVzGAQgASgOMiguc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlRG9tYWluU3RhdHVzEhcKD3R4dF9yZWNvcmRfbmFtZRgFIAEoCRIYChB0eHRfcmVjb3JkX3ZhbHVlGAYgASgJEi8KC3ZlcmlmaWVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgpjcmVhdGVkX2F0GAggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCIzChdEYXRhc291cmNlRGlyZWN0b3J5VGVhbRIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJIqYBCiFCZWdpbkRhdGFzb3VyY2VBY2NvdW50TGlua1JlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARI0Cgljb25uZWN0b3IYAiABKAlCIbpIHnIcEAIYIDIWXlthLXpdW2EtejAtOS1dezEsMzF9JBIeCgxyZWRpcmVjdF91cmkYAyABKAlCCLpIBXIDGIAQEhEKCXNvdXJjZV9pZBgEIAEoCSJ6CiJCZWdpbkRhdGFzb3VyY2VBY2NvdW50TGlua1Jlc3BvbnNlEhUKDWF1dGhvcml6ZV91cmwYASABKAkSDQoFc3RhdGUYAiABKAkSLgoKZXhwaXJlc19hdBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAidQokQ29tcGxldGVEYXRhc291cmNlQWNjb3VudExpbmtSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGQoFc3RhdGUYAiABKAlCCrpIB3IFEAEYgEASGAoEY29kZRgDIAEoCUIKukgHcgUQARj/ASJeCiVDb21wbGV0ZURhdGFzb3VyY2VBY2NvdW50TGlua1Jlc3BvbnNlEjUKBGxpbmsYASABKAsyJy5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VBY2NvdW50TGluayI/CiNMaXN0TXlEYXRhc291cmNlQWNjb3VudExpbmtzUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBIl4KJExpc3RNeURhdGFzb3VyY2VBY2NvdW50TGlua3NSZXNwb25zZRI2CgVsaW5rcxgBIAMoCzInLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZUFjY291bnRMaW5rIlQKIkRlbGV0ZURhdGFzb3VyY2VBY2NvdW50TGlua1JlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIUCgJpZBgCIAEoCUIIukgFcgOwAQEiJQojRGVsZXRlRGF0YXNvdXJjZUFjY291bnRMaW5rUmVzcG9uc2UiOQodR2V0RGF0YXNvdXJjZURpcmVjdG9yeVJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABASKLAgoeR2V0RGF0YXNvdXJjZURpcmVjdG9yeVJlc3BvbnNlEjYKBWxpbmtzGAEgAygLMicuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlQWNjb3VudExpbmsSOgoIYmluZGluZ3MYAiADKAsyKC5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VHcm91cEJpbmRpbmcSOwoHZG9tYWlucxgDIAMoCzIqLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVZlcmlmaWVkRG9tYWluEjgKBXRlYW1zGAQgAygLMikuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlRGlyZWN0b3J5VGVhbSKuAQoaQmluZERhdGFzb3VyY2VHcm91cFJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARI0Cgljb25uZWN0b3IYAiABKAlCIbpIHnIcEAIYIDIWXlthLXpdW2EtejAtOS1dezEsMzF9JBIlChFwcm92aWRlcl9ncm91cF9pZBgDIAEoCUIKukgHcgUQARj/ARIZCgd0ZWFtX2lkGAQgASgJQgi6SAVyA7ABASJYChtCaW5kRGF0YXNvdXJjZUdyb3VwUmVzcG9uc2USOQoHYmluZGluZxgBIAEoCzIoLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZUdyb3VwQmluZGluZyJOChxVbmJpbmREYXRhc291cmNlR3JvdXBSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESFAoCaWQYAiABKAlCCLpIBXIDsAEBIh8KHVVuYmluZERhdGFzb3VyY2VHcm91cFJlc3BvbnNlIlQKHENsYWltRGF0YXNvdXJjZURvbWFpblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIaCgZkb21haW4YAiABKAlCCrpIB3IFEAMY/QEiWwodQ2xhaW1EYXRhc291cmNlRG9tYWluUmVzcG9uc2USOgoGZG9tYWluGAEgASgLMiouc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlVmVyaWZpZWREb21haW4iTwodVmVyaWZ5RGF0YXNvdXJjZURvbWFpblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIUCgJpZBgCIAEoCUIIukgFcgOwAQEiXAoeVmVyaWZ5RGF0YXNvdXJjZURvbWFpblJlc3BvbnNlEjoKBmRvbWFpbhgBIAEoCzIqLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVZlcmlmaWVkRG9tYWluIk8KHURlbGV0ZURhdGFzb3VyY2VEb21haW5SZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESFAoCaWQYAiABKAlCCLpIBXIDsAEBIiAKHkRlbGV0ZURhdGFzb3VyY2VEb21haW5SZXNwb25zZSKeAgoQU291cmNlRGVsZWdhdGlvbhIKCgJpZBgBIAEoCRIRCglzb3VyY2VfaWQYAiABKAkSFAoMcHJpbmNpcGFsX2lkGAMgASgJEg4KBm1vZHVsZRgEIAEoCRIPCgdiaW5kaW5nGAUgASgJEi4KCmNyZWF0ZWRfYXQYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnJldm9rZWRfYXQYByABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEkAKCnJldm9jYXRpb24YCCABKA4yLC5zYWFzLmFjY291bnRzLnYxLlNvdXJjZURlbGVnYXRpb25SZXZvY2F0aW9uEhIKCnJldm9rZWRfYnkYCSABKAkigQEKHExpc3RTb3VyY2VEZWxlZ2F0aW9uc1JlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIgCglzb3VyY2VfaWQYAiABKAlCCLpIBXIDsAEBSACIAQESFwoPaW5jbHVkZV9yZXZva2VkGAMgASgIQgwKCl9zb3VyY2VfaWQiWAodTGlzdFNvdXJjZURlbGVnYXRpb25zUmVzcG9uc2USNwoLZGVsZWdhdGlvbnMYASADKAsyIi5zYWFzLmFjY291bnRzLnYxLlNvdXJjZURlbGVnYXRpb24iTwodUmV2b2tlU291cmNlRGVsZWdhdGlvblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIUCgJpZBgCIAEoCUIIukgFcgOwAQEiWAoeUmV2b2tlU291cmNlRGVsZWdhdGlvblJlc3BvbnNlEjYKCmRlbGVnYXRpb24YASABKAsyIi5zYWFzLmFjY291bnRzLnYxLlNvdXJjZURlbGVnYXRpb24iMgoPVHJpZ2dlcklkZW50aXR5Eg4KBnNvdXJjZRgBIAEoCRIPCgdiaW5kaW5nGAIgASgJIoQBChdEZWNsYXJlZFRyaWdnZXJTY2hlZHVsZRIMCgRjcm9uGAEgASgJEhAKCHRpbWV6b25lGAIgASgJEhIKCm1pc3NlZF9ydW4YAyABKAkSDwoHb3ZlcmxhcBgEIAEoCRIQCghkZWFkbGluZRgFIAEoAxISCgppbnB1dF9qc29uGAYgASgJIucCChpUcmlnZ2VyRGVsZWdhdGlvblJlZmVyZW5jZRIRCglyZWZlcmVuY2UYASABKAkSFQoNZGVsZWdhdGlvbl9pZBgCIAEoCRIOCgZtb2R1bGUYAyABKAkSDwoHYmluZGluZxgEIAEoCRIQCghhdWRpZW5jZRgFIAEoCRIOCgZ0ZW5hbnQYBiABKAkSGgoSb3duZXJfcHJpbmNpcGFsX2lkGAcgASgJEg4KBnNjb3BlcxgIIAMoCRIOCgZhY3RpdmUYCSABKAgSLgoKY3JlYXRlZF9hdBgKIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKcmV2b2tlZF9hdBgLIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASQAoKcmV2b2NhdGlvbhgMIAEoDjIsLnNhYXMuYWNjb3VudHMudjEuU291cmNlRGVsZWdhdGlvblJldm9jYXRpb24ivgEKE1RyaWdnZXJDaGlsZENvbnRleHQSFQoNZGVsZWdhdGlvbl9pZBgBIAEoCRIOCgZtb2R1bGUYAiABKAkSDwoHYmluZGluZxgDIAEoCRIQCghhdWRpZW5jZRgEIAEoCRIOCgZzY29wZXMYBSADKAkSDgoGbG9va3VwGAYgASgIEi0KCW1pbnRlZF9hdBgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASDgoGcmVjb3JkGAggASgJIs0CChhTb3VyY2VUcmlnZ2VyRGVjbGFyYXRpb24SDwoHcmVsZWFzZRgBIAEoCRIRCglvcGVyYXRpb24YAiABKAkSOwoIc2NoZWR1bGUYAyABKAsyKS5zYWFzLmFjY291bnRzLnYxLkRlY2xhcmVkVHJpZ2dlclNjaGVkdWxlEjMKCGlkZW50aXR5GAQgASgLMiEuc2Fhcy5hY2NvdW50cy52MS5UcmlnZ2VySWRlbnRpdHkSDgoGZGlnZXN0GAUgASgJEjYKEmxhc3Rfb2NjdXJyZW5jZV9hdBgGIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASNgoSbmV4dF9vY2N1cnJlbmNlX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIbChN1bmRlY2xhcmFibGVfcmVhc29uGAggASgJIlYKHURlbGVnYXRlU291cmNlVHJpZ2dlcnNSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGwoJc291cmNlX2lkGAIgASgJQgi6SAVyA7ABASJjCh5EZWxlZ2F0ZVNvdXJjZVRyaWdnZXJzUmVzcG9uc2USQQoLZGVsZWdhdGlvbnMYASADKAsyLC5zYWFzLmFjY291bnRzLnYxLlRyaWdnZXJEZWxlZ2F0aW9uUmVmZXJlbmNlIlsKIkdldFNvdXJjZVRyaWdnZXJEZWNsYXJhdGlvblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIbCglzb3VyY2VfaWQYAiABKAlCCLpIBXIDsAEBIuIBCiNHZXRTb3VyY2VUcmlnZ2VyRGVjbGFyYXRpb25SZXNwb25zZRI/CgtkZWNsYXJhdGlvbhgBIAEoCzIqLnNhYXMuYWNjb3VudHMudjEuU291cmNlVHJpZ2dlckRlY2xhcmF0aW9uEkEKC2RlbGVnYXRpb25zGAIgAygLMiwuc2Fhcy5hY2NvdW50cy52MS5UcmlnZ2VyRGVsZWdhdGlvblJlZmVyZW5jZRI3CghjaGlsZHJlbhgDIAMoCzIlLnNhYXMuYWNjb3VudHMudjEuVHJpZ2dlckNoaWxkQ29udGV4dCJuChZSZWNvbmNpbGVTb3VyY2VSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGwoJc291cmNlX2lkGAIgASgJQgi6SAVyA7ABARIdCgllZmZlY3RfaWQYAyABKAlCCrpIB3IFEAEYgAEiTAoXUmVjb25jaWxlU291cmNlUmVzcG9uc2USEQoJZWZmZWN0X2lkGAEgASgJEg4KBnN0YXR1cxgCIAEoCRIOCgZqb2JfaWQYAyABKAkiPQocTG9va3VwUmVjb25jaWxlU291cmNlUmVxdWVzdBIdCgllZmZlY3RfaWQYASABKAlCCrpIB3IFEAEYgAEi8QIKD1NvdXJjZU9wZXJhdGlvbhIMCgRuYW1lGAEgASgJEhMKC2Rlc2NyaXB0aW9uGAIgASgJEg4KBm1ldGhvZBgDIAEoCRIMCgRwYXRoGAQgASgJEg0KBXF1ZXJ5GAUgAygJEi0KDGlucHV0X3NjaGVtYRgGIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSLgoNb3V0cHV0X3NjaGVtYRgHIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSOAoGZWZmZWN0GAggASgOMiguc2Fhcy5hY2NvdW50cy52MS5Tb3VyY2VPcGVyYXRpb24uRWZmZWN0EhgKEG1heF9vdXRwdXRfYnl0ZXMYCSABKA0SDgoGZGlnZXN0GAogASgJIksKBkVmZmVjdBIWChJFRkZFQ1RfVU5TUEVDSUZJRUQQABIUChBFRkZFQ1RfUkVBRF9PTkxZEAESEwoPRUZGRUNUX01VVEFUSU9OEAIijgEKHkRlY2xhcmVTb3VyY2VPcGVyYXRpb25zUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhsKCXNvdXJjZV9pZBgCIAEoCUIIukgFcgOwAQESNQoKb3BlcmF0aW9ucxgDIAMoCzIhLnNhYXMuYWNjb3VudHMudjEuU291cmNlT3BlcmF0aW9uIlgKH0RlY2xhcmVTb3VyY2VPcGVyYXRpb25zUmVzcG9uc2USNQoKb3BlcmF0aW9ucxgBIAMoCzIhLnNhYXMuYWNjb3VudHMudjEuU291cmNlT3BlcmF0aW9uIlQKG0xpc3RTb3VyY2VPcGVyYXRpb25zUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhsKCXNvdXJjZV9pZBgCIAEoCUIIukgFcgOwAQEiVQocTGlzdFNvdXJjZU9wZXJhdGlvbnNSZXNwb25zZRI1CgpvcGVyYXRpb25zGAEgAygLMiEuc2Fhcy5hY2NvdW50cy52MS5Tb3VyY2VPcGVyYXRpb24irgIKHEludm9rZVNvdXJjZU9wZXJhdGlvblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIbCglzb3VyY2VfaWQYAiABKAlCCLpIBXIDsAEBEjAKCW9wZXJhdGlvbhgDIAEoCUIdukgachgyFl5bYS16XVthLXowLTlfXXswLDYzfSQSHQoKaW5wdXRfanNvbhgEIAEoCUIJukgGcgQogIAEEhsKCWVmZmVjdF9pZBgFIAEoCUIIukgFcgMogAESSQobZXhwZWN0ZWRfZGVjbGFyYXRpb25fZGlnZXN0GAYgASgJQh+6SBxyGjIYXihzaGEyNTY6WzAtOWEtZl17NjR9KT8kSACIAQFCHgocX2V4cGVjdGVkX2RlY2xhcmF0aW9uX2RpZ2VzdCJ/ChZTb3VyY2VPcGVyYXRpb25SZWNlaXB0EhEKCWVmZmVjdF9pZBgBIAEoCRIUCgxjb21taXR0ZWRfYXQYAiABKAkSDgoGc3RhdHVzGAMgASgJEhcKD3Byb3ZpZGVyX3N0YXR1cxgEIAEoDRITCgtvdXRwdXRfanNvbhgFIAEoCSJvCh1JbnZva2VTb3VyY2VPcGVyYXRpb25SZXNwb25zZRITCgtvdXRwdXRfanNvbhgBIAEoCRI5CgdyZWNlaXB0GAIgASgLMiguc2Fhcy5hY2NvdW50cy52MS5Tb3VyY2VPcGVyYXRpb25SZWNlaXB0IkMKIkxvb2t1cEludm9rZVNvdXJjZU9wZXJhdGlvblJlcXVlc3QSHQoJZWZmZWN0X2lkGAEgASgJQgq6SAdyBRABKIABInkKI1BydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGwoJc291cmNlX2lkGAIgASgJQgi6SAVyA7ABARIbCgllZmZlY3RfaWQYAyABKAlCCLpIBXIDKIABIlsKJFBydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXNwb25zZRIRCgllZmZlY3RfaWQYASABKAkSEAoIcmVjZWlwdHMYAiABKAMSDgoGc3RhdHVzGAMgASgJIkoKKUxvb2t1cFBydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXF1ZXN0Eh0KCWVmZmVjdF9pZBgBIAEoCUIKukgHcgUQASiAASq3AQoSRGF0YXNvdXJjZVByb3ZpZGVyEiMKH0RBVEFTT1VSQ0VfUFJPVklERVJfVU5TUEVDSUZJRUQQABIeChpEQVRBU09VUkNFX1BST1ZJREVSX0dJVEhVQhABEhsKF0RBVEFTT1VSQ0VfUFJPVklERVJfQVBJEAISHwobREFUQVNPVVJDRV9QUk9WSURFUl9DUkFXTEVSEAMSHgoaREFUQVNPVVJDRV9QUk9WSURFUl9VUExPQUQQBCqRAQoQRGF0YXNvdXJjZVN0YXR1cxIhCh1EQVRBU09VUkNFX1NUQVRVU19VTlNQRUNJRklFRBAAEhwKGERBVEFTT1VSQ0VfU1RBVFVTX0FDVElWRRABEhwKGERBVEFTT1VSQ0VfU1RBVFVTX1BBVVNFRBACEh4KGkRBVEFTT1VSQ0VfU1RBVFVTX0RFR1JBREVEEAMqvAEKFkRhdGFzb3VyY2VMaXZlRGVsaXZlcnkSKAokREFUQVNPVVJDRV9MSVZFX0RFTElWRVJZX1VOU1BFQ0lGSUVEEAASIQodREFUQVNPVVJDRV9MSVZFX0RFTElWRVJZX05PTkUQARIrCidEQVRBU09VUkNFX0xJVkVfREVMSVZFUllfU09VUkNFX1dFQkhPT0sQAhIoCiREQVRBU09VUkNFX0xJVkVfREVMSVZFUllfQVBQX1dFQkhPT0sQAyrWAQoRQXBpQ3JlZGVudGlhbEtpbmQSIwofQVBJX0NSRURFTlRJQUxfS0lORF9VTlNQRUNJRklFRBAAEh4KGkFQSV9DUkVERU5USUFMX0tJTkRfQkVBUkVSEAESHQoZQVBJX0NSRURFTlRJQUxfS0lORF9CQVNJQxACEh4KGkFQSV9DUkVERU5USUFMX0tJTkRfSEVBREVSEAMSHQoZQVBJX0NSRURFTlRJQUxfS0lORF9RVUVSWRAEEh4KGkFQSV9DUkVERU5USUFMX0tJTkRfT0FVVEgyEAUqpgIKHERhdGFzb3VyY2VDb25uZWN0b3JJbnRlcmZhY2USLgoqREFUQVNPVVJDRV9DT05ORUNUT1JfSU5URVJGQUNFX1VOU1BFQ0lGSUVEEAASKAokREFUQVNPVVJDRV9DT05ORUNUT1JfSU5URVJGQUNFX0ZJTEVTEAESKAokREFUQVNPVVJDRV9DT05ORUNUT1JfSU5URVJGQUNFX1BBR0VTEAISKgomREFUQVNPVVJDRV9DT05ORUNUT1JfSU5URVJGQUNFX1JFQ09SRFMQAxIrCidEQVRBU09VUkNFX0NPTk5FQ1RPUl9JTlRFUkZBQ0VfTUVTU0FHRVMQBBIpCiVEQVRBU09VUkNFX0NPTk5FQ1RPUl9JTlRFUkZBQ0VfRVZFTlRTEAUq7AEKGERhdGFzb3VyY2VDcmVkZW50aWFsTW9kZRIqCiZEQVRBU09VUkNFX0NSRURFTlRJQUxfTU9ERV9VTlNQRUNJRklFRBAAEiMKH0RBVEFTT1VSQ0VfQ1JFREVOVElBTF9NT0RFX05PTkUQARImCiJEQVRBU09VUkNFX0NSRURFTlRJQUxfTU9ERV9PUkdfQVBQEAISKQolREFUQVNPVVJDRV9DUkVERU5USUFMX01PREVfVVNFUl9PQVVUSBADEiwKKERBVEFTT1VSQ0VfQ1JFREVOVElBTF9NT0RFX1NUQVRJQ19TRUNSRVQQBCqXAQoWRGF0YXNvdXJjZVJlYWRlcnNNb2RlbBIoCiREQVRBU09VUkNFX1JFQURFUlNfTU9ERUxfVU5TUEVDSUZJRUQQABIqCiZEQVRBU09VUkNFX1JFQURFUlNfTU9ERUxfU09VUkNFX1NDT1BFRBABEicKI0RBVEFTT1VSQ0VfUkVBREVSU19NT0RFTF9UUkFOU0xBVEVEEAIq7gEKD1NvdXJjZVN5bmNQaGFzZRIhCh1TT1VSQ0VfU1lOQ19QSEFTRV9VTlNQRUNJRklFRBAAEhwKGFNPVVJDRV9TWU5DX1BIQVNFX1FVRVVFRBABEh4KGlNPVVJDRV9TWU5DX1BIQVNFX0ZFVENISU5HEAISHgoaU09VUkNFX1NZTkNfUEhBU0VfQ09NUElMRUQQAxIgChxTT1VSQ0VfU1lOQ19QSEFTRV9IQU5ERURfT0ZGEAQSGgoWU09VUkNFX1NZTkNfUEhBU0VfRE9ORRAFEhwKGFNPVVJDRV9TWU5DX1BIQVNFX0ZBSUxFRBAGKqYDChdTb3VyY2VTeW5jRmFpbHVyZVJlYXNvbhIqCiZTT1VSQ0VfU1lOQ19GQUlMVVJFX1JFQVNPTl9VTlNQRUNJRklFRBAAEisKJ1NPVVJDRV9TWU5DX0ZBSUxVUkVfUkVBU09OX1JBVEVfTElNSVRFRBABEikKJVNPVVJDRV9TWU5DX0ZBSUxVUkVfUkVBU09OX0NSRURFTlRJQUwQAhIsCihTT1VSQ0VfU1lOQ19GQUlMVVJFX1JFQVNPTl9BQ0NFU1NfREVOSUVEEAMSKAokU09VUkNFX1NZTkNfRkFJTFVSRV9SRUFTT05fTk9UX0ZPVU5EEAQSKAokU09VUkNFX1NZTkNfRkFJTFVSRV9SRUFTT05fVE9PX0xBUkdFEAUSLworU09VUkNFX1NZTkNfRkFJTFVSRV9SRUFTT05fSE9TVF9VTkFWQUlMQUJMRRAGEiQKIFNPVVJDRV9TWU5DX0ZBSUxVUkVfUkVBU09OX09USEVSEAcSLgoqU09VUkNFX1NZTkNfRkFJTFVSRV9SRUFTT05fREVMSVZFUllfRkFJTEVEEAgqwgEKEVNvdXJjZVN5bmNUcmlnZ2VyEiMKH1NPVVJDRV9TWU5DX1RSSUdHRVJfVU5TUEVDSUZJRUQQABIeChpTT1VSQ0VfU1lOQ19UUklHR0VSX01BTlVBTBABEiEKHVNPVVJDRV9TWU5DX1RSSUdHRVJfU0NIRURVTEVEEAISHwobU09VUkNFX1NZTkNfVFJJR0dFUl9XRUJIT09LEAMSJAogU09VUkNFX1NZTkNfVFJJR0dFUl9SRUNPTkZJR1VSRUQQBCqPAQoWRGF0YXNvdXJjZURvbWFpblN0YXR1cxIoCiREQVRBU09VUkNFX0RPTUFJTl9TVEFUVVNfVU5TUEVDSUZJRUQQABIkCiBEQVRBU09VUkNFX0RPTUFJTl9TVEFUVVNfUEVORElORxABEiUKIURBVEFTT1VSQ0VfRE9NQUlOX1NUQVRVU19WRVJJRklFRBACKpUDChpTb3VyY2VEZWxlZ2F0aW9uUmV2b2NhdGlvbhIsCihTT1VSQ0VfREVMRUdBVElPTl9SRVZPQ0FUSU9OX1VOU1BFQ0lGSUVEEAASKAokU09VUkNFX0RFTEVHQVRJT05fUkVWT0NBVElPTl9SRVZPS0VEEAESKQolU09VUkNFX0RFTEVHQVRJT05fUkVWT0NBVElPTl9SRVBMQUNFRBACEi8KK1NPVVJDRV9ERUxFR0FUSU9OX1JFVk9DQVRJT05fU09VUkNFX0RFTEVURUQQAxIvCitTT1VSQ0VfREVMRUdBVElPTl9SRVZPQ0FUSU9OX01FTUJFUl9SRU1PVkVEEAQSMAosU09VUkNFX0RFTEVHQVRJT05fUkVWT0NBVElPTl9QRVJNSVNTSU9OX0xPU1QQBRIuCipTT1VSQ0VfREVMRUdBVElPTl9SRVZPQ0FUSU9OX1VTRVJfSU5BQ1RJVkUQBhIwCixTT1VSQ0VfREVMRUdBVElPTl9SRVZPQ0FUSU9OX0JJTkRJTkdfQ0hBTkdFRBAHMrE5ChFEYXRhc291cmNlU2VydmljZRK5AwocUHJ1bmVTb3VyY2VPcGVyYXRpb25SZWNlaXB0cxI1LnNhYXMuYWNjb3VudHMudjEuUHJ1bmVTb3VyY2VPcGVyYXRpb25SZWNlaXB0c1JlcXVlc3QaNi5zYWFzLmFjY291bnRzLnYxLlBydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXNwb25zZSKpAsLzGGwIAhADGhFkYXRhc291cmNlOmludm9rZSIRZGF0YXNvdXJjZTppbnZva2UqDwoJc291cmNlX2lkEAQYAzABOiMKH3NhYXMuZGF0YXNvdXJjZS5yZWNlaXB0cy5wcnVuZWQQAkADSARQA1gDYAHa8xi0AQoCCDwSAwjYBBgFIgIIAioLVU5BVkFJTEFCTEUqElJFU09VUkNFX0VYSEFVU1RFRCoHQUJPUlRFRDIPc2Fhcy1kYXRhc291cmNlSkYvc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlU2VydmljZS9Mb29rdXBQcnVuZVNvdXJjZU9wZXJhdGlvblJlY2VpcHRzUIAgWIAgYAFyGAoGc291cmNlEgZpbnZva2USBHJlYWQYARLmAQoiTG9va3VwUHJ1bmVTb3VyY2VPcGVyYXRpb25SZWNlaXB0cxI7LnNhYXMuYWNjb3VudHMudjEuTG9va3VwUHJ1bmVTb3VyY2VPcGVyYXRpb25SZWNlaXB0c1JlcXVlc3QaNi5zYWFzLmFjY291bnRzLnYxLlBydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXNwb25zZSJLwvMYRwgCEAMaD2RhdGFzb3VyY2U6cmVhZCIPZGF0YXNvdXJjZTpyZWFkKg8KCWVmZmVjdF9pZBAEGAMwAToCEAFAAUgDUANYA2ABEvwDChVJbnZva2VTb3VyY2VPcGVyYXRpb24SLi5zYWFzLmFjY291bnRzLnYxLkludm9rZVNvdXJjZU9wZXJhdGlvblJlcXVlc3QaLy5zYWFzLmFjY291bnRzLnYxLkludm9rZVNvdXJjZU9wZXJhdGlvblJlc3BvbnNlIoEDwvMYbggCEAMaEWRhdGFzb3VyY2U6aW52b2tlIhFkYXRhc291cmNlOmludm9rZSoPCglzb3VyY2VfaWQQBBgDMAE6JQohc2Fhcy5kYXRhc291cmNlLm9wZXJhdGlvbi5pbnZva2VkEARAA0gEUARYBGAB2vMYrwEKAgg8EgMI2AQYBSICCAIqC1VOQVZBSUxBQkxFKhJSRVNPVVJDRV9FWEhBVVNURUQqB0FCT1JURUQyD3NhYXMtZGF0YXNvdXJjZUo/L3NhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVNlcnZpY2UvTG9va3VwSW52b2tlU291cmNlT3BlcmF0aW9uUICABFiAgEBgAXIYCgZzb3VyY2USBmludm9rZRIEcmVhZBgBgtPkkwJVOgEqIlAvdjEvb3JnYW5pemF0aW9ucy97b3JnX2lkfS9kYXRhc291cmNlcy97c291cmNlX2lkfS9vcGVyYXRpb25zL3tvcGVyYXRpb259Omludm9rZRKHAgobTG9va3VwSW52b2tlU291cmNlT3BlcmF0aW9uEjQuc2Fhcy5hY2NvdW50cy52MS5Mb29rdXBJbnZva2VTb3VyY2VPcGVyYXRpb25SZXF1ZXN0Gi8uc2Fhcy5hY2NvdW50cy52MS5JbnZva2VTb3VyY2VPcGVyYXRpb25SZXNwb25zZSKAAcLzGEcIAhADGg9kYXRhc291cmNlOnJlYWQiD2RhdGFzb3VyY2U6cmVhZCoPCgllZmZlY3RfaWQQBBgDMAE6AhABQAFIA1ADWARgAYLT5JMCLxItL3YxL2RhdGFzb3VyY2Utb3BlcmF0aW9uLXJlY2VpcHRzL3tlZmZlY3RfaWR9EoUCChRMaXN0U291cmNlT3BlcmF0aW9ucxItLnNhYXMuYWNjb3VudHMudjEuTGlzdFNvdXJjZU9wZXJhdGlvbnNSZXF1ZXN0Gi4uc2Fhcy5hY2NvdW50cy52MS5MaXN0U291cmNlT3BlcmF0aW9uc1Jlc3BvbnNlIo0BwvMYRAgCEAMaD2RhdGFzb3VyY2U6cmVhZCIPZGF0YXNvdXJjZTpyZWFkKgwKBm9yZ19pZBACGAEwAToCEAFAAUgDUANYA2ABgtPkkwI/Ej0vdjEvb3JnYW5pemF0aW9ucy97b3JnX2lkfS9kYXRhc291cmNlcy97c291cmNlX2lkfS9vcGVyYXRpb25zEpMCChdEZWNsYXJlU291cmNlT3BlcmF0aW9ucxIwLnNhYXMuYWNjb3VudHMudjEuRGVjbGFyZVNvdXJjZU9wZXJhdGlvbnNSZXF1ZXN0GjEuc2Fhcy5hY2NvdW50cy52MS5EZWNsYXJlU291cmNlT3BlcmF0aW9uc1Jlc3BvbnNlIpIBwvMYRggCEAQqDAoGb3JnX2lkEAIYATABOiYKInNhYXMuZGF0YXNvdXJjZS5vcGVyYXRpb24uZGVjbGFyZWQQAkABSARQA1gDYAGC0+STAkI6ASoiPS92MS9vcmdhbml6YXRpb25zL3tvcmdfaWR9L2RhdGFzb3VyY2VzL3tzb3VyY2VfaWR9L29wZXJhdGlvbnMSrAEKD0FkZEdpdEh1YlNvdXJjZRIoLnNhYXMuYWNjb3VudHMudjEuQWRkR2l0SHViU291cmNlUmVxdWVzdBopLnNhYXMuYWNjb3VudHMudjEuQWRkR2l0SHViU291cmNlUmVzcG9uc2UiRMLzGEAIAhAEKgwKBm9yZ19pZBACGAEwATogChxzYWFzLmRhdGFzb3VyY2Uuc291cmNlLmFkZGVkEAJAAUgEUARYA2ABEpoBCglBZGRTb3VyY2USIi5zYWFzLmFjY291bnRzLnYxLkFkZFNvdXJjZVJlcXVlc3QaIy5zYWFzLmFjY291bnRzLnYxLkFkZFNvdXJjZVJlc3BvbnNlIkTC8xhACAIQBCoMCgZvcmdfaWQQAhgBMAE6IAocc2Fhcy5kYXRhc291cmNlLnNvdXJjZS5hZGRlZBACQAFIBFAEWANgARKlAQoMVXBkYXRlU291cmNlEiUuc2Fhcy5hY2NvdW50cy52MS5VcGRhdGVTb3VyY2VSZXF1ZXN0GiYuc2Fhcy5hY2NvdW50cy52MS5VcGRhdGVTb3VyY2VSZXNwb25zZSJGwvMYQggCEAQqDAoGb3JnX2lkEAIYATABOiIKHnNhYXMuZGF0YXNvdXJjZS5zb3VyY2UudXBkYXRlZBACQAFIBFACWANgARKPAQoUR2V0RGF0YXNvdXJjZUNhdGFsb2cSLS5zYWFzLmFjY291bnRzLnYxLkdldERhdGFzb3VyY2VDYXRhbG9nUmVxdWVzdBouLnNhYXMuYWNjb3VudHMudjEuR2V0RGF0YXNvdXJjZUNhdGFsb2dSZXNwb25zZSIYwvMYFAgCEAEwAToCEAFAAUgDUAJYAmABEoIBCgtMaXN0U291cmNlcxIkLnNhYXMuYWNjb3VudHMudjEuTGlzdFNvdXJjZXNSZXF1ZXN0GiUuc2Fhcy5hY2NvdW50cy52MS5MaXN0U291cmNlc1Jlc3BvbnNlIibC8xgiCAIQAyoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARJ8CglHZXRTb3VyY2USIi5zYWFzLmFjY291bnRzLnYxLkdldFNvdXJjZVJlcXVlc3QaIy5zYWFzLmFjY291bnRzLnYxLkdldFNvdXJjZVJlc3BvbnNlIibC8xgiCAIQAyoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARKeAQoKU3luY1NvdXJjZRIjLnNhYXMuYWNjb3VudHMudjEuU3luY1NvdXJjZVJlcXVlc3QaJC5zYWFzLmFjY291bnRzLnYxLlN5bmNTb3VyY2VSZXNwb25zZSJFwvMYQQgCEAQqDAoGb3JnX2lkEAIYATABOiEKHXNhYXMuZGF0YXNvdXJjZS5zb3VyY2Uuc3luY2VkEAJAAUgEUARYAmABEogBCg1HZXRTb3VyY2VTeW5jEiYuc2Fhcy5hY2NvdW50cy52MS5HZXRTb3VyY2VTeW5jUmVxdWVzdBonLnNhYXMuYWNjb3VudHMudjEuR2V0U291cmNlU3luY1Jlc3BvbnNlIibC8xgiCAIQBCoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARKlAQoMRGVsZXRlU291cmNlEiUuc2Fhcy5hY2NvdW50cy52MS5EZWxldGVTb3VyY2VSZXF1ZXN0GiYuc2Fhcy5hY2NvdW50cy52MS5EZWxldGVTb3VyY2VSZXNwb25zZSJGwvMYQggCEAQqDAoGb3JnX2lkEAIYATABOiIKHnNhYXMuZGF0YXNvdXJjZS5zb3VyY2UucmVtb3ZlZBACQAFIBFACWAJgARLEAQoTQmVnaW5HaXRIdWJBcHBTZXR1cBIsLnNhYXMuYWNjb3VudHMudjEuQmVnaW5HaXRIdWJBcHBTZXR1cFJlcXVlc3QaLS5zYWFzLmFjY291bnRzLnYxLkJlZ2luR2l0SHViQXBwU2V0dXBSZXNwb25zZSJQwvMYTAgCEAQqDAoGb3JnX2lkEAIYATABOiwKKHNhYXMuZGF0YXNvdXJjZS5naXRodWJfYXBwLnNldHVwX3N0YXJ0ZWQQAkABSARQAlgEYAESzwEKFkNvbXBsZXRlR2l0SHViQXBwU2V0dXASLy5zYWFzLmFjY291bnRzLnYxLkNvbXBsZXRlR2l0SHViQXBwU2V0dXBSZXF1ZXN0GjAuc2Fhcy5hY2NvdW50cy52MS5Db21wbGV0ZUdpdEh1YkFwcFNldHVwUmVzcG9uc2UiUsLzGE4IAhAEKgwKBm9yZ19pZBACGAEwATouCipzYWFzLmRhdGFzb3VyY2UuZ2l0aHViX2FwcC5zZXR1cF9jb21wbGV0ZWQQAkABSARQBFgDYAESzQEKGE1pZ3JhdGVHaXRIdWJTb3VyY2VUb0FwcBIxLnNhYXMuYWNjb3VudHMudjEuTWlncmF0ZUdpdEh1YlNvdXJjZVRvQXBwUmVxdWVzdBoyLnNhYXMuYWNjb3VudHMudjEuTWlncmF0ZUdpdEh1YlNvdXJjZVRvQXBwUmVzcG9uc2UiSsLzGEYIAhAEKgwKBm9yZ19pZBACGAEwATomCiJzYWFzLmRhdGFzb3VyY2UuY3JlZGVudGlhbC51cGRhdGVkEAJAAUgEUAJYA2ABEtUBChpCZWdpbkRhdGFzb3VyY2VBY2NvdW50TGluaxIzLnNhYXMuYWNjb3VudHMudjEuQmVnaW5EYXRhc291cmNlQWNjb3VudExpbmtSZXF1ZXN0GjQuc2Fhcy5hY2NvdW50cy52MS5CZWdpbkRhdGFzb3VyY2VBY2NvdW50TGlua1Jlc3BvbnNlIkzC8xhICAIQAyoMCgZvcmdfaWQQAhgBMAE6KAokc2Fhcy5kYXRhc291cmNlLmFjY291bnRfbGlua19zdGFydGVkEAJAAUgEUAJYBGABEtgBCh1Db21wbGV0ZURhdGFzb3VyY2VBY2NvdW50TGluaxI2LnNhYXMuYWNjb3VudHMudjEuQ29tcGxldGVEYXRhc291cmNlQWNjb3VudExpbmtSZXF1ZXN0Gjcuc2Fhcy5hY2NvdW50cy52MS5Db21wbGV0ZURhdGFzb3VyY2VBY2NvdW50TGlua1Jlc3BvbnNlIkbC8xhCCAIQAyoMCgZvcmdfaWQQAhgBMAE6Igoec2Fhcy5kYXRhc291cmNlLmFjY291bnRfbGlua2VkEAJAAUgEUARYA2ABErUBChxMaXN0TXlEYXRhc291cmNlQWNjb3VudExpbmtzEjUuc2Fhcy5hY2NvdW50cy52MS5MaXN0TXlEYXRhc291cmNlQWNjb3VudExpbmtzUmVxdWVzdBo2LnNhYXMuYWNjb3VudHMudjEuTGlzdE15RGF0YXNvdXJjZUFjY291bnRMaW5rc1Jlc3BvbnNlIibC8xgiCAIQAyoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARLUAQobRGVsZXRlRGF0YXNvdXJjZUFjY291bnRMaW5rEjQuc2Fhcy5hY2NvdW50cy52MS5EZWxldGVEYXRhc291cmNlQWNjb3VudExpbmtSZXF1ZXN0GjUuc2Fhcy5hY2NvdW50cy52MS5EZWxldGVEYXRhc291cmNlQWNjb3VudExpbmtSZXNwb25zZSJIwvMYRAgCEAMqDAoGb3JnX2lkEAIYATABOiQKIHNhYXMuZGF0YXNvdXJjZS5hY2NvdW50X3VubGlua2VkEAJAAUgEUAJYA2ABEqMBChZHZXREYXRhc291cmNlRGlyZWN0b3J5Ei8uc2Fhcy5hY2NvdW50cy52MS5HZXREYXRhc291cmNlRGlyZWN0b3J5UmVxdWVzdBowLnNhYXMuYWNjb3VudHMudjEuR2V0RGF0YXNvdXJjZURpcmVjdG9yeVJlc3BvbnNlIibC8xgiCAIQBCoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARK3AQoTQmluZERhdGFzb3VyY2VHcm91cBIsLnNhYXMuYWNjb3VudHMudjEuQmluZERhdGFzb3VyY2VHcm91cFJlcXVlc3QaLS5zYWFzLmFjY291bnRzLnYxLkJpbmREYXRhc291cmNlR3JvdXBSZXNwb25zZSJDwvMYPwgCEAQqDAoGb3JnX2lkEAIYATABOh8KG3NhYXMuZGF0YXNvdXJjZS5ncm91cF9ib3VuZBACQAFIBFACWANgARK/AQoVVW5iaW5kRGF0YXNvdXJjZUdyb3VwEi4uc2Fhcy5hY2NvdW50cy52MS5VbmJpbmREYXRhc291cmNlR3JvdXBSZXF1ZXN0Gi8uc2Fhcy5hY2NvdW50cy52MS5VbmJpbmREYXRhc291cmNlR3JvdXBSZXNwb25zZSJFwvMYQQgCEAQqDAoGb3JnX2lkEAIYATABOiEKHXNhYXMuZGF0YXNvdXJjZS5ncm91cF91bmJvdW5kEAJAAUgEUAJYA2ABEsABChVDbGFpbURhdGFzb3VyY2VEb21haW4SLi5zYWFzLmFjY291bnRzLnYxLkNsYWltRGF0YXNvdXJjZURvbWFpblJlcXVlc3QaLy5zYWFzLmFjY291bnRzLnYxLkNsYWltRGF0YXNvdXJjZURvbWFpblJlc3BvbnNlIkbC8xhCCAIQBCoMCgZvcmdfaWQQAhgBMAE6Igoec2Fhcy5kYXRhc291cmNlLmRvbWFpbl9jbGFpbWVkEAJAAUgEUAJYA2ABEsQBChZWZXJpZnlEYXRhc291cmNlRG9tYWluEi8uc2Fhcy5hY2NvdW50cy52MS5WZXJpZnlEYXRhc291cmNlRG9tYWluUmVxdWVzdBowLnNhYXMuYWNjb3VudHMudjEuVmVyaWZ5RGF0YXNvdXJjZURvbWFpblJlc3BvbnNlIkfC8xhDCAIQBCoMCgZvcmdfaWQQAhgBMAE6Iwofc2Fhcy5kYXRhc291cmNlLmRvbWFpbl92ZXJpZmllZBACQAFIBFACWANgARLDAQoWRGVsZXRlRGF0YXNvdXJjZURvbWFpbhIvLnNhYXMuYWNjb3VudHMudjEuRGVsZXRlRGF0YXNvdXJjZURvbWFpblJlcXVlc3QaMC5zYWFzLmFjY291bnRzLnYxLkRlbGV0ZURhdGFzb3VyY2VEb21haW5SZXNwb25zZSJGwvMYQggCEAQqDAoGb3JnX2lkEAIYATABOiIKHnNhYXMuZGF0YXNvdXJjZS5kb21haW5fcmVtb3ZlZBACQAFIBFACWANgARKgAQoVTGlzdFNvdXJjZURlbGVnYXRpb25zEi4uc2Fhcy5hY2NvdW50cy52MS5MaXN0U291cmNlRGVsZWdhdGlvbnNSZXF1ZXN0Gi8uc2Fhcy5hY2NvdW50cy52MS5MaXN0U291cmNlRGVsZWdhdGlvbnNSZXNwb25zZSImwvMYIggCEAQqDAoGb3JnX2lkEAIYATABOgIQAUABSANQAlgDYAESxwEKFlJldm9rZVNvdXJjZURlbGVnYXRpb24SLy5zYWFzLmFjY291bnRzLnYxLlJldm9rZVNvdXJjZURlbGVnYXRpb25SZXF1ZXN0GjAuc2Fhcy5hY2NvdW50cy52MS5SZXZva2VTb3VyY2VEZWxlZ2F0aW9uUmVzcG9uc2UiSsLzGEYIAhAEKgwKBm9yZ19pZBACGAEwATomCiJzYWFzLmRhdGFzb3VyY2UuZGVsZWdhdGlvbi5yZXZva2VkEAJAAUgEUAJYA2ABEusBChZEZWxlZ2F0ZVNvdXJjZVRyaWdnZXJzEi8uc2Fhcy5hY2NvdW50cy52MS5EZWxlZ2F0ZVNvdXJjZVRyaWdnZXJzUmVxdWVzdBowLnNhYXMuYWNjb3VudHMudjEuRGVsZWdhdGVTb3VyY2VUcmlnZ2Vyc1Jlc3BvbnNlIm7C8xhqCAIQBCoMCgZvcmdfaWQQAhgBMAE6Sgoic2Fhcy5kYXRhc291cmNlLmRlbGVnYXRpb24uY3JlYXRlZAoic2Fhcy5kYXRhc291cmNlLmRlbGVnYXRpb24ucmV2b2tlZBACQAFIBFACWANgARKyAQobR2V0U291cmNlVHJpZ2dlckRlY2xhcmF0aW9uEjQuc2Fhcy5hY2NvdW50cy52MS5HZXRTb3VyY2VUcmlnZ2VyRGVjbGFyYXRpb25SZXF1ZXN0GjUuc2Fhcy5hY2NvdW50cy52MS5HZXRTb3VyY2VUcmlnZ2VyRGVjbGFyYXRpb25SZXNwb25zZSImwvMYIggCEAQqDAoGb3JnX2lkEAIYATABOgIQAUABSANQAlgDYAESgwMKD1JlY29uY2lsZVNvdXJjZRIoLnNhYXMuYWNjb3VudHMudjEuUmVjb25jaWxlU291cmNlUmVxdWVzdBopLnNhYXMuYWNjb3VudHMudjEuUmVjb25jaWxlU291cmNlUmVzcG9uc2UimgLC8xhqCAIQAxoRZGF0YXNvdXJjZTppbnZva2UiEWRhdGFzb3VyY2U6aW52b2tlKg8KCXNvdXJjZV9pZBAEGAMwATohCh1zYWFzLmRhdGFzb3VyY2Uuc291cmNlLnN5bmNlZBACQANIBFACWAJgAdrzGKcBCgIIPBIDCNgEGAUiAggCKgtVTkFWQUlMQUJMRSoSUkVTT1VSQ0VfRVhIQVVTVEVEKgdBQk9SVEVEMg9zYWFzLWRhdGFzb3VyY2VKOS9zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VTZXJ2aWNlL0xvb2t1cFJlY29uY2lsZVNvdXJjZVCAIFiAIGABchgKBnNvdXJjZRIGaW52b2tlEgRyZWFkGAESvwEKFUxvb2t1cFJlY29uY2lsZVNvdXJjZRIuLnNhYXMuYWNjb3VudHMudjEuTG9va3VwUmVjb25jaWxlU291cmNlUmVxdWVzdBopLnNhYXMuYWNjb3VudHMudjEuUmVjb25jaWxlU291cmNlUmVzcG9uc2UiS8LzGEcIAhADGg9kYXRhc291cmNlOnJlYWQiD2RhdGFzb3VyY2U6cmVhZCoPCgllZmZlY3RfaWQQBBgDMAE6AhABQAFIA1ACWAJgAWIGcHJvdG8z", [file_buf_validate_validate, file_codefly_runnable_v0_options, file_google_api_annotations, file_google_protobuf_duration, file_google_protobuf_struct, file_google_protobuf_timestamp, file_saas_jobs_v1_jobs, file_saas_policy_v1_options]); + fileDesc("CiFzYWFzL2FjY291bnRzL3YxL2RhdGFzb3VyY2UucHJvdG8SEHNhYXMuYWNjb3VudHMudjEiXgoWR2l0SHViRGF0YXNvdXJjZUNvbmZpZxIMCgRyZXBvGAEgASgJEg0KBXBhdGhzGAIgAygJEg4KBmJyYW5jaBgDIAEoCRIXCg9maWxlX2V4dGVuc2lvbnMYBCADKAki9AEKD0FwaU9BdXRoMkNvbmZpZxI2CgVncmFudBgEIAEoDjInLnNhYXMuYWNjb3VudHMudjEuQXBpT0F1dGgyQ29uZmlnLkdyYW50EhEKCXRva2VuX3VybBgBIAEoCRIRCgljbGllbnRfaWQYAiABKAkSDgoGc2NvcGVzGAMgAygJInMKBUdyYW50EhUKEUdSQU5UX1VOU1BFQ0lGSUVEEAASFwoTR1JBTlRfUkVGUkVTSF9UT0tFThABEhwKGEdSQU5UX0NMSUVOVF9DUkVERU5USUFMUxACEhwKGEdSQU5UX0FVVEhPUklaQVRJT05fQ09ERRADIuoBChNBcGlEYXRhc291cmNlQ29uZmlnEhAKCGJhc2VfdXJsGAEgASgJEhUKDXJlc291cmNlX3BhdGgYAiABKAkSPAoPY3JlZGVudGlhbF9raW5kGAMgASgOMiMuc2Fhcy5hY2NvdW50cy52MS5BcGlDcmVkZW50aWFsS2luZBIZChFjcmVkZW50aWFsX2hlYWRlchgEIAEoCRIeChZjcmVkZW50aWFsX3F1ZXJ5X3BhcmFtGAUgASgJEjEKBm9hdXRoMhgGIAEoCzIhLnNhYXMuYWNjb3VudHMudjEuQXBpT0F1dGgyQ29uZmlnIkEKF0NyYXdsZXJEYXRhc291cmNlQ29uZmlnEhMKC3NpdGVtYXBfdXJsGAEgASgJEhEKCW1heF9wYWdlcxgCIAEoDSKrAgoWVXBsb2FkRGF0YXNvdXJjZUNvbmZpZxIQCghlbmRwb2ludBgBIAEoCRIOCgZyZWdpb24YAiABKAkSDgoGYnVja2V0GAMgASgJEg4KBnByZWZpeBgEIAEoCRIVCg1hY2Nlc3Nfa2V5X2lkGAUgASgJEhMKC21heF9vYmplY3RzGAYgASgNEisKEGluY2x1ZGVfcGF0dGVybnMYByADKAlCEbpIDpIBCxAQIgdyBRABGIACEisKEGV4Y2x1ZGVfcGF0dGVybnMYCCADKAlCEbpIDpIBCxAQIgdyBRABGIACEkkKD2ZpbGVfZXh0ZW5zaW9ucxgJIAMoCUIwukgtkgEqECAiJnIkEAIYITIeXlwuW0EtWmEtejAtOV1bQS1aYS16MC05Ll8tXSokIoMHCgpEYXRhc291cmNlEgoKAmlkGAEgASgJEg4KBm9yZ19pZBgCIAEoCRI2Cghwcm92aWRlchgDIAEoDjIkLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVByb3ZpZGVyEjgKBmdpdGh1YhgFIAEoCzIoLnNhYXMuYWNjb3VudHMudjEuR2l0SHViRGF0YXNvdXJjZUNvbmZpZxIyCgZzdGF0dXMYBiABKA4yIi5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VTdGF0dXMSGgoSd2ViaG9va19jb25maWd1cmVkGAcgASgIEi4KCmNyZWF0ZWRfYXQYCCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjIKDmxhc3Rfc3luY2VkX2F0GAogASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIyCgNhcGkYCyABKAsyJS5zYWFzLmFjY291bnRzLnYxLkFwaURhdGFzb3VyY2VDb25maWcSOgoHY3Jhd2xlchgMIAEoCzIpLnNhYXMuYWNjb3VudHMudjEuQ3Jhd2xlckRhdGFzb3VyY2VDb25maWcSOAoGdXBsb2FkGA0gASgLMiguc2Fhcy5hY2NvdW50cy52MS5VcGxvYWREYXRhc291cmNlQ29uZmlnEhgKEGJvdW5kYXJ5X25vZGVfaWQYDiABKAkSNAoQbGFzdF9pbmdlc3RlZF9hdBgPIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASHAoUbGFzdF9pbmdlc3RlZF9jb21taXQYECABKAkSFQoNc3RhdHVzX3JlYXNvbhgRIAEoCRIWCg5ib3VuZGFyeV9sYWJlbBgSIAEoCRISCgpjb25mb3JtYW50GBMgASgIEhcKD2NvbmZvcm1hbmNlX2dhcBgUIAEoCRI/Cg1saXZlX2RlbGl2ZXJ5GBUgASgOMiguc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlTGl2ZURlbGl2ZXJ5EjUKEnJlY29uY2lsZV9pbnRlcnZhbBgWIAEoCzIZLmdvb2dsZS5wcm90b2J1Zi5EdXJhdGlvbkoECAQQBVIRdGFyZ2V0X2NvbGxlY3Rpb24isgMKFkFkZEdpdEh1YlNvdXJjZVJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARI7CgRyZXBvGAIgASgJQi26SCpyKBADGP8BMiFeW0EtWmEtejAtOV8uLV0rL1tBLVphLXowLTlfLi1dKyQSIAoFcGF0aHMYAyADKAlCEbpIDpIBCxBAIgdyBRABGIAEEhgKBmJyYW5jaBgEIAEoCUIIukgFcgMY/wESJAoQYm91bmRhcnlfbm9kZV9pZBgIIAEoCUIIukgFcgOwAQFIABImChBjb2xsZWN0aW9uX2xhYmVsGAkgASgJQgq6SAdyBRABGP8BSAASHgoMYWNjZXNzX3Rva2VuGAYgASgJQgi6SAVyAxiACBIgCg53ZWJob29rX3NlY3JldBgHIAEoCUIIukgFcgMYgAgSSQoPZmlsZV9leHRlbnNpb25zGAogAygJQjC6SC2SASoQICImciQQAhghMh5eXC5bQS1aYS16MC05XVtBLVphLXowLTkuXy1dKiRCEQoIYm91bmRhcnkSBbpIAggBSgQIBRAGUhF0YXJnZXRfY29sbGVjdGlvbiJLChdBZGRHaXRIdWJTb3VyY2VSZXNwb25zZRIwCgpkYXRhc291cmNlGAEgASgLMhwuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlIsgEChBBZGRTb3VyY2VSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESQgoIcHJvdmlkZXIYAiABKA4yJC5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VQcm92aWRlckIKukgHggEEEAEgABI6CgZnaXRodWIYBCABKAsyKC5zYWFzLmFjY291bnRzLnYxLkdpdEh1YkRhdGFzb3VyY2VDb25maWdIABI0CgNhcGkYBSABKAsyJS5zYWFzLmFjY291bnRzLnYxLkFwaURhdGFzb3VyY2VDb25maWdIABI8CgdjcmF3bGVyGAggASgLMikuc2Fhcy5hY2NvdW50cy52MS5DcmF3bGVyRGF0YXNvdXJjZUNvbmZpZ0gAEjoKBnVwbG9hZBgJIAEoCzIoLnNhYXMuYWNjb3VudHMudjEuVXBsb2FkRGF0YXNvdXJjZUNvbmZpZ0gAEiQKEGJvdW5kYXJ5X25vZGVfaWQYDCABKAlCCLpIBXIDsAEBSAESJgoQY29sbGVjdGlvbl9sYWJlbBgNIAEoCUIKukgHcgUQARj/AUgBEhwKCmNyZWRlbnRpYWwYBiABKAlCCLpIBXIDGIAgEiAKDndlYmhvb2tfc2VjcmV0GAcgASgJQgi6SAVyAxiACBImChRvYXV0aDJfY2xpZW50X3NlY3JldBgKIAEoCUIIukgFcgMYgAhCCAoGY29uZmlnQhEKCGJvdW5kYXJ5EgW6SAIIAUoECAMQBFIRdGFyZ2V0X2NvbGxlY3Rpb24iRQoRQWRkU291cmNlUmVzcG9uc2USMAoKZGF0YXNvdXJjZRgBIAEoCzIcLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZSLkAgoTVXBkYXRlU291cmNlUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhQKAmlkGAIgASgJQgi6SAVyA7ABARIYCgZicmFuY2gYAyABKAlCCLpIBXIDGP8BEiAKBXBhdGhzGAQgAygJQhG6SA6SAQsQQCIHcgUQARiABBJJCg9maWxlX2V4dGVuc2lvbnMYBSADKAlCMLpILZIBKhAgIiZyJBACGCEyHl5cLltBLVphLXowLTldW0EtWmEtejAtOS5fLV0qJBI8CghleHBlY3RlZBgGIAEoCzIiLnNhYXMuYWNjb3VudHMudjEuU291cmNlRmV0Y2hTY29wZUIGukgDyAEBEisKEGluY2x1ZGVfcGF0dGVybnMYByADKAlCEbpIDpIBCxAQIgdyBRABGIACEisKEGV4Y2x1ZGVfcGF0dGVybnMYCCADKAlCEbpIDpIBCxAQIgdyBRABGIACIs8BChBTb3VyY2VGZXRjaFNjb3BlEhgKBmJyYW5jaBgBIAEoCUIIukgFcgMY/wESIAoFcGF0aHMYAiADKAlCEbpIDpIBCxBAIgdyBRABGIAEEikKD2ZpbGVfZXh0ZW5zaW9ucxgDIAMoCUIQukgNkgEKECAiBnIEEAEYIRIpChBpbmNsdWRlX3BhdHRlcm5zGAQgAygJQg+6SAySAQkQECIFcgMYgAISKQoQZXhjbHVkZV9wYXR0ZXJucxgFIAMoCUIPukgMkgEJEBAiBXIDGIACIlgKFFVwZGF0ZVNvdXJjZVJlc3BvbnNlEjAKCmRhdGFzb3VyY2UYASABKAsyHC5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2USDgoGam9iX2lkGAIgASgJIloKFURhdGFzb3VyY2VDb25maWdGaWVsZBILCgNrZXkYASABKAkSFAoMZGlzcGxheV9uYW1lGAIgASgJEgwKBGhlbHAYAyABKAkSEAoIcmVxdWlyZWQYBCABKAgixAEKGURhdGFzb3VyY2VDb25uZWN0b3JCdWRnZXQSGgoSbWF4X2l0ZW1zX3Blcl9jYWxsGAEgASgNEhoKEm1heF9ieXRlc19wZXJfY2FsbBgCIAEoAxIWCg5tYXhfaXRlbV9ieXRlcxgDIAEoAxIdChVvcGVyYXRpb25zX3Blcl93aW5kb3cYBCABKA0SFgoOd2luZG93X3NlY29uZHMYBSABKAMSIAoYYmFja2dyb3VuZF9zaGFyZV9wZXJjZW50GAYgASgNIqoGChxEYXRhc291cmNlUHJvdmlkZXJEZXNjcmlwdG9yEjYKCHByb3ZpZGVyGAEgASgOMiQuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlUHJvdmlkZXISFAoMZGlzcGxheV9uYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEj4KDWNvbmZpZ19maWVsZHMYBCADKAsyJy5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VDb25maWdGaWVsZBIYChBzdXBwb3J0c193ZWJob29rGAUgASgIEkcKGnN1cHBvcnRlZF9jcmVkZW50aWFsX2tpbmRzGAYgAygOMiMuc2Fhcy5hY2NvdW50cy52MS5BcGlDcmVkZW50aWFsS2luZBIRCgljb25uZWN0b3IYByABKAkSQQoJaW50ZXJmYWNlGAggASgOMi4uc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlQ29ubmVjdG9ySW50ZXJmYWNlEkQKEGNyZWRlbnRpYWxfbW9kZXMYCSADKA4yKi5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VDcmVkZW50aWFsTW9kZRI/Cg1yZWFkZXJzX21vZGVsGAogASgOMiguc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlUmVhZGVyc01vZGVsEjsKBmJ1ZGdldBgLIAEoCzIrLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZUNvbm5lY3RvckJ1ZGdldBISCgpjb25mb3JtYW50GAwgASgIEhcKD2NvbmZvcm1hbmNlX2dhcBgNIAEoCRIbChNhY2NlcHRzX25ld19zb3VyY2VzGA4gASgIEiAKGGxpdmVfZGVsaXZlcnlfY29uZmlndXJlZBgPIAEoCBIaChJhY2NlcHRzX29wZXJhdGlvbnMYECABKAgSFgoOb3BlcmF0aW9uc19nYXAYESABKAkSGwoTb2F1dGhfYXV0aG9yaXplX3VybBgSIAEoCRIXCg9vYXV0aF90b2tlbl91cmwYEyABKAkSFAoMb2F1dGhfc2NvcGVzGBQgAygJIh0KG0dldERhdGFzb3VyY2VDYXRhbG9nUmVxdWVzdCJhChxHZXREYXRhc291cmNlQ2F0YWxvZ1Jlc3BvbnNlEkEKCXByb3ZpZGVycxgBIAMoCzIuLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVByb3ZpZGVyRGVzY3JpcHRvciIuChJMaXN0U291cmNlc1JlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABASJIChNMaXN0U291cmNlc1Jlc3BvbnNlEjEKC2RhdGFzb3VyY2VzGAEgAygLMhwuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlIkIKEEdldFNvdXJjZVJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIUCgJpZBgCIAEoCUIIukgFcgOwAQEiRQoRR2V0U291cmNlUmVzcG9uc2USMAoKZGF0YXNvdXJjZRgBIAEoCzIcLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZSJ2ChFTeW5jU291cmNlUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhQKAmlkGAIgASgJQgi6SAVyA7ABARIeCgxhY2Nlc3NfdG9rZW4YAyABKAlCCLpIBXIDGIAgEhEKCXJlY29ubmVjdBgEIAEoCCIkChJTeW5jU291cmNlUmVzcG9uc2USDgoGam9iX2lkGAEgASgJImoKFEdldFNvdXJjZVN5bmNSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGwoJc291cmNlX2lkGAIgASgJQgi6SAVyA7ABARIbCgZqb2JfaWQYAyABKAlCC7pICNgBAXIDsAEBIpUBChJTb3VyY2VTeW5jRGVsaXZlcnkSGAoGam9iX2lkGAEgASgJQgi6SAVyA7ABARItCgVzdGF0ZRgCIAEoDjIWLnNhYXMuam9icy52MS5Kb2JTdGF0ZUIGukgDyAEBEjYKCWV4ZWN1dGlvbhgDIAEoCzIjLnNhYXMuam9icy52MS5Kb2JFeGVjdXRpb25SZWZlcmVuY2UiiwEKEVNvdXJjZVN5bmNDaGFuZ2VzEg0KBWZpbGVzGAEgASgNEg0KBWFkZGVkGAIgASgNEhAKCG1vZGlmaWVkGAMgASgNEg8KB2RlbGV0ZWQYBCABKA0SEwoLc3BsaXRfa25vd24YBSABKAgSEAoIc25hcHNob3QYBiABKAgSDgoGY29tbWl0GAcgASgJIq0BChFTb3VyY2VTeW5jRmFpbHVyZRI5CgZyZWFzb24YASABKA4yKS5zYWFzLmFjY291bnRzLnYxLlNvdXJjZVN5bmNGYWlsdXJlUmVhc29uEgwKBGNvZGUYAiABKAkSDwoHbWVzc2FnZRgDIAEoCRIQCghyZXRyeWluZxgEIAEoCBIsCghyZXRyeV9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAihAQKElNvdXJjZVN5bmNQcm9ncmVzcxIwCgVwaGFzZRgBIAEoDjIhLnNhYXMuYWNjb3VudHMudjEuU291cmNlU3luY1BoYXNlEjQKB3RyaWdnZXIYAiABKA4yIy5zYWFzLmFjY291bnRzLnYxLlNvdXJjZVN5bmNUcmlnZ2VyEi0KCXF1ZXVlZF9hdBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLwoLZmV0Y2hpbmdfYXQYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi8KC2NvbXBpbGVkX2F0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIxCg1oYW5kZWRfb2ZmX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIvCgtmaW5pc2hlZF9hdBgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASNAoHY2hhbmdlcxgIIAEoCzIjLnNhYXMuYWNjb3VudHMudjEuU291cmNlU3luY0NoYW5nZXMSNAoHZmFpbHVyZRgJIAEoCzIjLnNhYXMuYWNjb3VudHMudjEuU291cmNlU3luY0ZhaWx1cmUSDwoHYXR0ZW1wdBgKIAEoDRIUCgxtYXhfYXR0ZW1wdHMYCyABKA0i0gEKFUdldFNvdXJjZVN5bmNSZXNwb25zZRIYCgZqb2JfaWQYASABKAlCCLpIBXIDsAEBEi0KBXN0YXRlGAIgASgOMhYuc2Fhcy5qb2JzLnYxLkpvYlN0YXRlQga6SAPIAQESOAoKZGVsaXZlcmllcxgDIAMoCzIkLnNhYXMuYWNjb3VudHMudjEuU291cmNlU3luY0RlbGl2ZXJ5EjYKCHByb2dyZXNzGAQgASgLMiQuc2Fhcy5hY2NvdW50cy52MS5Tb3VyY2VTeW5jUHJvZ3Jlc3MiRQoTRGVsZXRlU291cmNlUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhQKAmlkGAIgASgJQgi6SAVyA7ABASIWChREZWxldGVTb3VyY2VSZXNwb25zZSJWChNHaXRIdWJBcHBSZXBvc2l0b3J5EgwKBHJlcG8YASABKAkSFgoOZGVmYXVsdF9icmFuY2gYAiABKAkSGQoRYWxyZWFkeV9jb25uZWN0ZWQYAyABKAgiNgoaQmVnaW5HaXRIdWJBcHBTZXR1cFJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABASJxChtCZWdpbkdpdEh1YkFwcFNldHVwUmVzcG9uc2USEwoLaW5zdGFsbF91cmwYASABKAkSDQoFc3RhdGUYAiABKAkSLgoKZXhwaXJlc19hdBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAinAEKHUNvbXBsZXRlR2l0SHViQXBwU2V0dXBSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGQoFc3RhdGUYAiABKAlCCrpIB3IFEAEY/wESLAoPaW5zdGFsbGF0aW9uX2lkGAMgASgJQhO6SBByDhABGCAyCF5bMC05XSskEhgKBGNvZGUYBCABKAlCCrpIB3IFEAEY/wEidgoeQ29tcGxldGVHaXRIdWJBcHBTZXR1cFJlc3BvbnNlEhcKD2luc3RhbGxhdGlvbl9pZBgBIAEoCRI7CgxyZXBvc2l0b3JpZXMYAiADKAsyJS5zYWFzLmFjY291bnRzLnYxLkdpdEh1YkFwcFJlcG9zaXRvcnkiUQofTWlncmF0ZUdpdEh1YlNvdXJjZVRvQXBwUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhQKAmlkGAIgASgJQgi6SAVyA7ABASJUCiBNaWdyYXRlR2l0SHViU291cmNlVG9BcHBSZXNwb25zZRIwCgpkYXRhc291cmNlGAEgASgLMhwuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlIsQBChVEYXRhc291cmNlQWNjb3VudExpbmsSCgoCaWQYASABKAkSDgoGb3JnX2lkGAIgASgJEg8KB3VzZXJfaWQYAyABKAkSEQoJY29ubmVjdG9yGAQgASgJEhsKE3Byb3ZpZGVyX2FjY291bnRfaWQYBSABKAkSHgoWcHJvdmlkZXJfYWNjb3VudF9sb2dpbhgGIAEoCRIuCgpjcmVhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCK3AQoWRGF0YXNvdXJjZUdyb3VwQmluZGluZxIKCgJpZBgBIAEoCRIOCgZvcmdfaWQYAiABKAkSEQoJY29ubmVjdG9yGAMgASgJEhkKEXByb3ZpZGVyX2dyb3VwX2lkGAQgASgJEg8KB3RlYW1faWQYBSABKAkSEgoKY3JlYXRlZF9ieRgGIAEoCRIuCgpjcmVhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKUAgoYRGF0YXNvdXJjZVZlcmlmaWVkRG9tYWluEgoKAmlkGAEgASgJEg4KBm9yZ19pZBgCIAEoCRIOCgZkb21haW4YAyABKAkSOAoGc3RhdHVzGAQgASgOMiguc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlRG9tYWluU3RhdHVzEhcKD3R4dF9yZWNvcmRfbmFtZRgFIAEoCRIYChB0eHRfcmVjb3JkX3ZhbHVlGAYgASgJEi8KC3ZlcmlmaWVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgpjcmVhdGVkX2F0GAggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCIzChdEYXRhc291cmNlRGlyZWN0b3J5VGVhbRIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJIqYBCiFCZWdpbkRhdGFzb3VyY2VBY2NvdW50TGlua1JlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARI0Cgljb25uZWN0b3IYAiABKAlCIbpIHnIcEAIYIDIWXlthLXpdW2EtejAtOS1dezEsMzF9JBIeCgxyZWRpcmVjdF91cmkYAyABKAlCCLpIBXIDGIAQEhEKCXNvdXJjZV9pZBgEIAEoCSJ6CiJCZWdpbkRhdGFzb3VyY2VBY2NvdW50TGlua1Jlc3BvbnNlEhUKDWF1dGhvcml6ZV91cmwYASABKAkSDQoFc3RhdGUYAiABKAkSLgoKZXhwaXJlc19hdBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAidQokQ29tcGxldGVEYXRhc291cmNlQWNjb3VudExpbmtSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGQoFc3RhdGUYAiABKAlCCrpIB3IFEAEYgEASGAoEY29kZRgDIAEoCUIKukgHcgUQARj/ASJeCiVDb21wbGV0ZURhdGFzb3VyY2VBY2NvdW50TGlua1Jlc3BvbnNlEjUKBGxpbmsYASABKAsyJy5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VBY2NvdW50TGluayI/CiNMaXN0TXlEYXRhc291cmNlQWNjb3VudExpbmtzUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBIl4KJExpc3RNeURhdGFzb3VyY2VBY2NvdW50TGlua3NSZXNwb25zZRI2CgVsaW5rcxgBIAMoCzInLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZUFjY291bnRMaW5rIlQKIkRlbGV0ZURhdGFzb3VyY2VBY2NvdW50TGlua1JlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIUCgJpZBgCIAEoCUIIukgFcgOwAQEiJQojRGVsZXRlRGF0YXNvdXJjZUFjY291bnRMaW5rUmVzcG9uc2UiOQodR2V0RGF0YXNvdXJjZURpcmVjdG9yeVJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABASKLAgoeR2V0RGF0YXNvdXJjZURpcmVjdG9yeVJlc3BvbnNlEjYKBWxpbmtzGAEgAygLMicuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlQWNjb3VudExpbmsSOgoIYmluZGluZ3MYAiADKAsyKC5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VHcm91cEJpbmRpbmcSOwoHZG9tYWlucxgDIAMoCzIqLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVZlcmlmaWVkRG9tYWluEjgKBXRlYW1zGAQgAygLMikuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlRGlyZWN0b3J5VGVhbSKuAQoaQmluZERhdGFzb3VyY2VHcm91cFJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARI0Cgljb25uZWN0b3IYAiABKAlCIbpIHnIcEAIYIDIWXlthLXpdW2EtejAtOS1dezEsMzF9JBIlChFwcm92aWRlcl9ncm91cF9pZBgDIAEoCUIKukgHcgUQARj/ARIZCgd0ZWFtX2lkGAQgASgJQgi6SAVyA7ABASJYChtCaW5kRGF0YXNvdXJjZUdyb3VwUmVzcG9uc2USOQoHYmluZGluZxgBIAEoCzIoLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZUdyb3VwQmluZGluZyJOChxVbmJpbmREYXRhc291cmNlR3JvdXBSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESFAoCaWQYAiABKAlCCLpIBXIDsAEBIh8KHVVuYmluZERhdGFzb3VyY2VHcm91cFJlc3BvbnNlIlQKHENsYWltRGF0YXNvdXJjZURvbWFpblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIaCgZkb21haW4YAiABKAlCCrpIB3IFEAMY/QEiWwodQ2xhaW1EYXRhc291cmNlRG9tYWluUmVzcG9uc2USOgoGZG9tYWluGAEgASgLMiouc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlVmVyaWZpZWREb21haW4iTwodVmVyaWZ5RGF0YXNvdXJjZURvbWFpblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIUCgJpZBgCIAEoCUIIukgFcgOwAQEiXAoeVmVyaWZ5RGF0YXNvdXJjZURvbWFpblJlc3BvbnNlEjoKBmRvbWFpbhgBIAEoCzIqLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVZlcmlmaWVkRG9tYWluIk8KHURlbGV0ZURhdGFzb3VyY2VEb21haW5SZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESFAoCaWQYAiABKAlCCLpIBXIDsAEBIiAKHkRlbGV0ZURhdGFzb3VyY2VEb21haW5SZXNwb25zZSKeAgoQU291cmNlRGVsZWdhdGlvbhIKCgJpZBgBIAEoCRIRCglzb3VyY2VfaWQYAiABKAkSFAoMcHJpbmNpcGFsX2lkGAMgASgJEg4KBm1vZHVsZRgEIAEoCRIPCgdiaW5kaW5nGAUgASgJEi4KCmNyZWF0ZWRfYXQYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnJldm9rZWRfYXQYByABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEkAKCnJldm9jYXRpb24YCCABKA4yLC5zYWFzLmFjY291bnRzLnYxLlNvdXJjZURlbGVnYXRpb25SZXZvY2F0aW9uEhIKCnJldm9rZWRfYnkYCSABKAkigQEKHExpc3RTb3VyY2VEZWxlZ2F0aW9uc1JlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIgCglzb3VyY2VfaWQYAiABKAlCCLpIBXIDsAEBSACIAQESFwoPaW5jbHVkZV9yZXZva2VkGAMgASgIQgwKCl9zb3VyY2VfaWQiWAodTGlzdFNvdXJjZURlbGVnYXRpb25zUmVzcG9uc2USNwoLZGVsZWdhdGlvbnMYASADKAsyIi5zYWFzLmFjY291bnRzLnYxLlNvdXJjZURlbGVnYXRpb24iTwodUmV2b2tlU291cmNlRGVsZWdhdGlvblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIUCgJpZBgCIAEoCUIIukgFcgOwAQEiWAoeUmV2b2tlU291cmNlRGVsZWdhdGlvblJlc3BvbnNlEjYKCmRlbGVnYXRpb24YASABKAsyIi5zYWFzLmFjY291bnRzLnYxLlNvdXJjZURlbGVnYXRpb24iMgoPVHJpZ2dlcklkZW50aXR5Eg4KBnNvdXJjZRgBIAEoCRIPCgdiaW5kaW5nGAIgASgJIoQBChdEZWNsYXJlZFRyaWdnZXJTY2hlZHVsZRIMCgRjcm9uGAEgASgJEhAKCHRpbWV6b25lGAIgASgJEhIKCm1pc3NlZF9ydW4YAyABKAkSDwoHb3ZlcmxhcBgEIAEoCRIQCghkZWFkbGluZRgFIAEoAxISCgppbnB1dF9qc29uGAYgASgJIucCChpUcmlnZ2VyRGVsZWdhdGlvblJlZmVyZW5jZRIRCglyZWZlcmVuY2UYASABKAkSFQoNZGVsZWdhdGlvbl9pZBgCIAEoCRIOCgZtb2R1bGUYAyABKAkSDwoHYmluZGluZxgEIAEoCRIQCghhdWRpZW5jZRgFIAEoCRIOCgZ0ZW5hbnQYBiABKAkSGgoSb3duZXJfcHJpbmNpcGFsX2lkGAcgASgJEg4KBnNjb3BlcxgIIAMoCRIOCgZhY3RpdmUYCSABKAgSLgoKY3JlYXRlZF9hdBgKIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKcmV2b2tlZF9hdBgLIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASQAoKcmV2b2NhdGlvbhgMIAEoDjIsLnNhYXMuYWNjb3VudHMudjEuU291cmNlRGVsZWdhdGlvblJldm9jYXRpb24ivgEKE1RyaWdnZXJDaGlsZENvbnRleHQSFQoNZGVsZWdhdGlvbl9pZBgBIAEoCRIOCgZtb2R1bGUYAiABKAkSDwoHYmluZGluZxgDIAEoCRIQCghhdWRpZW5jZRgEIAEoCRIOCgZzY29wZXMYBSADKAkSDgoGbG9va3VwGAYgASgIEi0KCW1pbnRlZF9hdBgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASDgoGcmVjb3JkGAggASgJIs0CChhTb3VyY2VUcmlnZ2VyRGVjbGFyYXRpb24SDwoHcmVsZWFzZRgBIAEoCRIRCglvcGVyYXRpb24YAiABKAkSOwoIc2NoZWR1bGUYAyABKAsyKS5zYWFzLmFjY291bnRzLnYxLkRlY2xhcmVkVHJpZ2dlclNjaGVkdWxlEjMKCGlkZW50aXR5GAQgASgLMiEuc2Fhcy5hY2NvdW50cy52MS5UcmlnZ2VySWRlbnRpdHkSDgoGZGlnZXN0GAUgASgJEjYKEmxhc3Rfb2NjdXJyZW5jZV9hdBgGIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASNgoSbmV4dF9vY2N1cnJlbmNlX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIbChN1bmRlY2xhcmFibGVfcmVhc29uGAggASgJIlYKHURlbGVnYXRlU291cmNlVHJpZ2dlcnNSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGwoJc291cmNlX2lkGAIgASgJQgi6SAVyA7ABASJjCh5EZWxlZ2F0ZVNvdXJjZVRyaWdnZXJzUmVzcG9uc2USQQoLZGVsZWdhdGlvbnMYASADKAsyLC5zYWFzLmFjY291bnRzLnYxLlRyaWdnZXJEZWxlZ2F0aW9uUmVmZXJlbmNlIlsKIkdldFNvdXJjZVRyaWdnZXJEZWNsYXJhdGlvblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIbCglzb3VyY2VfaWQYAiABKAlCCLpIBXIDsAEBIuIBCiNHZXRTb3VyY2VUcmlnZ2VyRGVjbGFyYXRpb25SZXNwb25zZRI/CgtkZWNsYXJhdGlvbhgBIAEoCzIqLnNhYXMuYWNjb3VudHMudjEuU291cmNlVHJpZ2dlckRlY2xhcmF0aW9uEkEKC2RlbGVnYXRpb25zGAIgAygLMiwuc2Fhcy5hY2NvdW50cy52MS5UcmlnZ2VyRGVsZWdhdGlvblJlZmVyZW5jZRI3CghjaGlsZHJlbhgDIAMoCzIlLnNhYXMuYWNjb3VudHMudjEuVHJpZ2dlckNoaWxkQ29udGV4dCJuChZSZWNvbmNpbGVTb3VyY2VSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGwoJc291cmNlX2lkGAIgASgJQgi6SAVyA7ABARIdCgllZmZlY3RfaWQYAyABKAlCCrpIB3IFEAEYgAEiTAoXUmVjb25jaWxlU291cmNlUmVzcG9uc2USEQoJZWZmZWN0X2lkGAEgASgJEg4KBnN0YXR1cxgCIAEoCRIOCgZqb2JfaWQYAyABKAkiPQocTG9va3VwUmVjb25jaWxlU291cmNlUmVxdWVzdBIdCgllZmZlY3RfaWQYASABKAlCCrpIB3IFEAEYgAEi8QIKD1NvdXJjZU9wZXJhdGlvbhIMCgRuYW1lGAEgASgJEhMKC2Rlc2NyaXB0aW9uGAIgASgJEg4KBm1ldGhvZBgDIAEoCRIMCgRwYXRoGAQgASgJEg0KBXF1ZXJ5GAUgAygJEi0KDGlucHV0X3NjaGVtYRgGIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSLgoNb3V0cHV0X3NjaGVtYRgHIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSOAoGZWZmZWN0GAggASgOMiguc2Fhcy5hY2NvdW50cy52MS5Tb3VyY2VPcGVyYXRpb24uRWZmZWN0EhgKEG1heF9vdXRwdXRfYnl0ZXMYCSABKA0SDgoGZGlnZXN0GAogASgJIksKBkVmZmVjdBIWChJFRkZFQ1RfVU5TUEVDSUZJRUQQABIUChBFRkZFQ1RfUkVBRF9PTkxZEAESEwoPRUZGRUNUX01VVEFUSU9OEAIijgEKHkRlY2xhcmVTb3VyY2VPcGVyYXRpb25zUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhsKCXNvdXJjZV9pZBgCIAEoCUIIukgFcgOwAQESNQoKb3BlcmF0aW9ucxgDIAMoCzIhLnNhYXMuYWNjb3VudHMudjEuU291cmNlT3BlcmF0aW9uIlgKH0RlY2xhcmVTb3VyY2VPcGVyYXRpb25zUmVzcG9uc2USNQoKb3BlcmF0aW9ucxgBIAMoCzIhLnNhYXMuYWNjb3VudHMudjEuU291cmNlT3BlcmF0aW9uIlQKG0xpc3RTb3VyY2VPcGVyYXRpb25zUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhsKCXNvdXJjZV9pZBgCIAEoCUIIukgFcgOwAQEiVQocTGlzdFNvdXJjZU9wZXJhdGlvbnNSZXNwb25zZRI1CgpvcGVyYXRpb25zGAEgAygLMiEuc2Fhcy5hY2NvdW50cy52MS5Tb3VyY2VPcGVyYXRpb24irgIKHEludm9rZVNvdXJjZU9wZXJhdGlvblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIbCglzb3VyY2VfaWQYAiABKAlCCLpIBXIDsAEBEjAKCW9wZXJhdGlvbhgDIAEoCUIdukgachgyFl5bYS16XVthLXowLTlfXXswLDYzfSQSHQoKaW5wdXRfanNvbhgEIAEoCUIJukgGcgQogIAEEhsKCWVmZmVjdF9pZBgFIAEoCUIIukgFcgMogAESSQobZXhwZWN0ZWRfZGVjbGFyYXRpb25fZGlnZXN0GAYgASgJQh+6SBxyGjIYXihzaGEyNTY6WzAtOWEtZl17NjR9KT8kSACIAQFCHgocX2V4cGVjdGVkX2RlY2xhcmF0aW9uX2RpZ2VzdCJ/ChZTb3VyY2VPcGVyYXRpb25SZWNlaXB0EhEKCWVmZmVjdF9pZBgBIAEoCRIUCgxjb21taXR0ZWRfYXQYAiABKAkSDgoGc3RhdHVzGAMgASgJEhcKD3Byb3ZpZGVyX3N0YXR1cxgEIAEoDRITCgtvdXRwdXRfanNvbhgFIAEoCSJvCh1JbnZva2VTb3VyY2VPcGVyYXRpb25SZXNwb25zZRITCgtvdXRwdXRfanNvbhgBIAEoCRI5CgdyZWNlaXB0GAIgASgLMiguc2Fhcy5hY2NvdW50cy52MS5Tb3VyY2VPcGVyYXRpb25SZWNlaXB0IkMKIkxvb2t1cEludm9rZVNvdXJjZU9wZXJhdGlvblJlcXVlc3QSHQoJZWZmZWN0X2lkGAEgASgJQgq6SAdyBRABKIABInkKI1BydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGwoJc291cmNlX2lkGAIgASgJQgi6SAVyA7ABARIbCgllZmZlY3RfaWQYAyABKAlCCLpIBXIDKIABIlsKJFBydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXNwb25zZRIRCgllZmZlY3RfaWQYASABKAkSEAoIcmVjZWlwdHMYAiABKAMSDgoGc3RhdHVzGAMgASgJIkoKKUxvb2t1cFBydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXF1ZXN0Eh0KCWVmZmVjdF9pZBgBIAEoCUIKukgHcgUQASiAASq3AQoSRGF0YXNvdXJjZVByb3ZpZGVyEiMKH0RBVEFTT1VSQ0VfUFJPVklERVJfVU5TUEVDSUZJRUQQABIeChpEQVRBU09VUkNFX1BST1ZJREVSX0dJVEhVQhABEhsKF0RBVEFTT1VSQ0VfUFJPVklERVJfQVBJEAISHwobREFUQVNPVVJDRV9QUk9WSURFUl9DUkFXTEVSEAMSHgoaREFUQVNPVVJDRV9QUk9WSURFUl9VUExPQUQQBCqRAQoQRGF0YXNvdXJjZVN0YXR1cxIhCh1EQVRBU09VUkNFX1NUQVRVU19VTlNQRUNJRklFRBAAEhwKGERBVEFTT1VSQ0VfU1RBVFVTX0FDVElWRRABEhwKGERBVEFTT1VSQ0VfU1RBVFVTX1BBVVNFRBACEh4KGkRBVEFTT1VSQ0VfU1RBVFVTX0RFR1JBREVEEAMqvAEKFkRhdGFzb3VyY2VMaXZlRGVsaXZlcnkSKAokREFUQVNPVVJDRV9MSVZFX0RFTElWRVJZX1VOU1BFQ0lGSUVEEAASIQodREFUQVNPVVJDRV9MSVZFX0RFTElWRVJZX05PTkUQARIrCidEQVRBU09VUkNFX0xJVkVfREVMSVZFUllfU09VUkNFX1dFQkhPT0sQAhIoCiREQVRBU09VUkNFX0xJVkVfREVMSVZFUllfQVBQX1dFQkhPT0sQAyrWAQoRQXBpQ3JlZGVudGlhbEtpbmQSIwofQVBJX0NSRURFTlRJQUxfS0lORF9VTlNQRUNJRklFRBAAEh4KGkFQSV9DUkVERU5USUFMX0tJTkRfQkVBUkVSEAESHQoZQVBJX0NSRURFTlRJQUxfS0lORF9CQVNJQxACEh4KGkFQSV9DUkVERU5USUFMX0tJTkRfSEVBREVSEAMSHQoZQVBJX0NSRURFTlRJQUxfS0lORF9RVUVSWRAEEh4KGkFQSV9DUkVERU5USUFMX0tJTkRfT0FVVEgyEAUqpgIKHERhdGFzb3VyY2VDb25uZWN0b3JJbnRlcmZhY2USLgoqREFUQVNPVVJDRV9DT05ORUNUT1JfSU5URVJGQUNFX1VOU1BFQ0lGSUVEEAASKAokREFUQVNPVVJDRV9DT05ORUNUT1JfSU5URVJGQUNFX0ZJTEVTEAESKAokREFUQVNPVVJDRV9DT05ORUNUT1JfSU5URVJGQUNFX1BBR0VTEAISKgomREFUQVNPVVJDRV9DT05ORUNUT1JfSU5URVJGQUNFX1JFQ09SRFMQAxIrCidEQVRBU09VUkNFX0NPTk5FQ1RPUl9JTlRFUkZBQ0VfTUVTU0FHRVMQBBIpCiVEQVRBU09VUkNFX0NPTk5FQ1RPUl9JTlRFUkZBQ0VfRVZFTlRTEAUq7AEKGERhdGFzb3VyY2VDcmVkZW50aWFsTW9kZRIqCiZEQVRBU09VUkNFX0NSRURFTlRJQUxfTU9ERV9VTlNQRUNJRklFRBAAEiMKH0RBVEFTT1VSQ0VfQ1JFREVOVElBTF9NT0RFX05PTkUQARImCiJEQVRBU09VUkNFX0NSRURFTlRJQUxfTU9ERV9PUkdfQVBQEAISKQolREFUQVNPVVJDRV9DUkVERU5USUFMX01PREVfVVNFUl9PQVVUSBADEiwKKERBVEFTT1VSQ0VfQ1JFREVOVElBTF9NT0RFX1NUQVRJQ19TRUNSRVQQBCqXAQoWRGF0YXNvdXJjZVJlYWRlcnNNb2RlbBIoCiREQVRBU09VUkNFX1JFQURFUlNfTU9ERUxfVU5TUEVDSUZJRUQQABIqCiZEQVRBU09VUkNFX1JFQURFUlNfTU9ERUxfU09VUkNFX1NDT1BFRBABEicKI0RBVEFTT1VSQ0VfUkVBREVSU19NT0RFTF9UUkFOU0xBVEVEEAIq7gEKD1NvdXJjZVN5bmNQaGFzZRIhCh1TT1VSQ0VfU1lOQ19QSEFTRV9VTlNQRUNJRklFRBAAEhwKGFNPVVJDRV9TWU5DX1BIQVNFX1FVRVVFRBABEh4KGlNPVVJDRV9TWU5DX1BIQVNFX0ZFVENISU5HEAISHgoaU09VUkNFX1NZTkNfUEhBU0VfQ09NUElMRUQQAxIgChxTT1VSQ0VfU1lOQ19QSEFTRV9IQU5ERURfT0ZGEAQSGgoWU09VUkNFX1NZTkNfUEhBU0VfRE9ORRAFEhwKGFNPVVJDRV9TWU5DX1BIQVNFX0ZBSUxFRBAGKqYDChdTb3VyY2VTeW5jRmFpbHVyZVJlYXNvbhIqCiZTT1VSQ0VfU1lOQ19GQUlMVVJFX1JFQVNPTl9VTlNQRUNJRklFRBAAEisKJ1NPVVJDRV9TWU5DX0ZBSUxVUkVfUkVBU09OX1JBVEVfTElNSVRFRBABEikKJVNPVVJDRV9TWU5DX0ZBSUxVUkVfUkVBU09OX0NSRURFTlRJQUwQAhIsCihTT1VSQ0VfU1lOQ19GQUlMVVJFX1JFQVNPTl9BQ0NFU1NfREVOSUVEEAMSKAokU09VUkNFX1NZTkNfRkFJTFVSRV9SRUFTT05fTk9UX0ZPVU5EEAQSKAokU09VUkNFX1NZTkNfRkFJTFVSRV9SRUFTT05fVE9PX0xBUkdFEAUSLworU09VUkNFX1NZTkNfRkFJTFVSRV9SRUFTT05fSE9TVF9VTkFWQUlMQUJMRRAGEiQKIFNPVVJDRV9TWU5DX0ZBSUxVUkVfUkVBU09OX09USEVSEAcSLgoqU09VUkNFX1NZTkNfRkFJTFVSRV9SRUFTT05fREVMSVZFUllfRkFJTEVEEAgqwgEKEVNvdXJjZVN5bmNUcmlnZ2VyEiMKH1NPVVJDRV9TWU5DX1RSSUdHRVJfVU5TUEVDSUZJRUQQABIeChpTT1VSQ0VfU1lOQ19UUklHR0VSX01BTlVBTBABEiEKHVNPVVJDRV9TWU5DX1RSSUdHRVJfU0NIRURVTEVEEAISHwobU09VUkNFX1NZTkNfVFJJR0dFUl9XRUJIT09LEAMSJAogU09VUkNFX1NZTkNfVFJJR0dFUl9SRUNPTkZJR1VSRUQQBCqPAQoWRGF0YXNvdXJjZURvbWFpblN0YXR1cxIoCiREQVRBU09VUkNFX0RPTUFJTl9TVEFUVVNfVU5TUEVDSUZJRUQQABIkCiBEQVRBU09VUkNFX0RPTUFJTl9TVEFUVVNfUEVORElORxABEiUKIURBVEFTT1VSQ0VfRE9NQUlOX1NUQVRVU19WRVJJRklFRBACKpUDChpTb3VyY2VEZWxlZ2F0aW9uUmV2b2NhdGlvbhIsCihTT1VSQ0VfREVMRUdBVElPTl9SRVZPQ0FUSU9OX1VOU1BFQ0lGSUVEEAASKAokU09VUkNFX0RFTEVHQVRJT05fUkVWT0NBVElPTl9SRVZPS0VEEAESKQolU09VUkNFX0RFTEVHQVRJT05fUkVWT0NBVElPTl9SRVBMQUNFRBACEi8KK1NPVVJDRV9ERUxFR0FUSU9OX1JFVk9DQVRJT05fU09VUkNFX0RFTEVURUQQAxIvCitTT1VSQ0VfREVMRUdBVElPTl9SRVZPQ0FUSU9OX01FTUJFUl9SRU1PVkVEEAQSMAosU09VUkNFX0RFTEVHQVRJT05fUkVWT0NBVElPTl9QRVJNSVNTSU9OX0xPU1QQBRIuCipTT1VSQ0VfREVMRUdBVElPTl9SRVZPQ0FUSU9OX1VTRVJfSU5BQ1RJVkUQBhIwCixTT1VSQ0VfREVMRUdBVElPTl9SRVZPQ0FUSU9OX0JJTkRJTkdfQ0hBTkdFRBAHMtc7ChFEYXRhc291cmNlU2VydmljZRK5AwocUHJ1bmVTb3VyY2VPcGVyYXRpb25SZWNlaXB0cxI1LnNhYXMuYWNjb3VudHMudjEuUHJ1bmVTb3VyY2VPcGVyYXRpb25SZWNlaXB0c1JlcXVlc3QaNi5zYWFzLmFjY291bnRzLnYxLlBydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXNwb25zZSKpAsLzGGwIAhADGhFkYXRhc291cmNlOmludm9rZSIRZGF0YXNvdXJjZTppbnZva2UqDwoJc291cmNlX2lkEAQYAzABOiMKH3NhYXMuZGF0YXNvdXJjZS5yZWNlaXB0cy5wcnVuZWQQAkADSARQA1gDYAHa8xi0AQoCCDwSAwjYBBgFIgIIAioLVU5BVkFJTEFCTEUqElJFU09VUkNFX0VYSEFVU1RFRCoHQUJPUlRFRDIPc2Fhcy1kYXRhc291cmNlSkYvc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlU2VydmljZS9Mb29rdXBQcnVuZVNvdXJjZU9wZXJhdGlvblJlY2VpcHRzUIAgWIAgYAFyGAoGc291cmNlEgZpbnZva2USBHJlYWQYARLmAQoiTG9va3VwUHJ1bmVTb3VyY2VPcGVyYXRpb25SZWNlaXB0cxI7LnNhYXMuYWNjb3VudHMudjEuTG9va3VwUHJ1bmVTb3VyY2VPcGVyYXRpb25SZWNlaXB0c1JlcXVlc3QaNi5zYWFzLmFjY291bnRzLnYxLlBydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXNwb25zZSJLwvMYRwgCEAMaD2RhdGFzb3VyY2U6cmVhZCIPZGF0YXNvdXJjZTpyZWFkKg8KCWVmZmVjdF9pZBAEGAMwAToCEAFAAUgDUANYA2ABEvwDChVJbnZva2VTb3VyY2VPcGVyYXRpb24SLi5zYWFzLmFjY291bnRzLnYxLkludm9rZVNvdXJjZU9wZXJhdGlvblJlcXVlc3QaLy5zYWFzLmFjY291bnRzLnYxLkludm9rZVNvdXJjZU9wZXJhdGlvblJlc3BvbnNlIoEDwvMYbggCEAMaEWRhdGFzb3VyY2U6aW52b2tlIhFkYXRhc291cmNlOmludm9rZSoPCglzb3VyY2VfaWQQBBgDMAE6JQohc2Fhcy5kYXRhc291cmNlLm9wZXJhdGlvbi5pbnZva2VkEARAA0gEUARYBGAB2vMYrwEKAgg8EgMI2AQYBSICCAIqC1VOQVZBSUxBQkxFKhJSRVNPVVJDRV9FWEhBVVNURUQqB0FCT1JURUQyD3NhYXMtZGF0YXNvdXJjZUo/L3NhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVNlcnZpY2UvTG9va3VwSW52b2tlU291cmNlT3BlcmF0aW9uUICABFiAgEBgAXIYCgZzb3VyY2USBmludm9rZRIEcmVhZBgBgtPkkwJVOgEqIlAvdjEvb3JnYW5pemF0aW9ucy97b3JnX2lkfS9kYXRhc291cmNlcy97c291cmNlX2lkfS9vcGVyYXRpb25zL3tvcGVyYXRpb259Omludm9rZRKHAgobTG9va3VwSW52b2tlU291cmNlT3BlcmF0aW9uEjQuc2Fhcy5hY2NvdW50cy52MS5Mb29rdXBJbnZva2VTb3VyY2VPcGVyYXRpb25SZXF1ZXN0Gi8uc2Fhcy5hY2NvdW50cy52MS5JbnZva2VTb3VyY2VPcGVyYXRpb25SZXNwb25zZSKAAcLzGEcIAhADGg9kYXRhc291cmNlOnJlYWQiD2RhdGFzb3VyY2U6cmVhZCoPCgllZmZlY3RfaWQQBBgDMAE6AhABQAFIA1ADWARgAYLT5JMCLxItL3YxL2RhdGFzb3VyY2Utb3BlcmF0aW9uLXJlY2VpcHRzL3tlZmZlY3RfaWR9EqMCChxWZXJpZnlTb3VyY2VPcGVyYXRpb25SZWNlaXB0Ei4uc2Fhcy5hY2NvdW50cy52MS5JbnZva2VTb3VyY2VPcGVyYXRpb25SZXF1ZXN0Gi8uc2Fhcy5hY2NvdW50cy52MS5JbnZva2VTb3VyY2VPcGVyYXRpb25SZXNwb25zZSKhAcLzGGoIAhADGg9kYXRhc291cmNlOnJlYWQiD2RhdGFzb3VyY2U6cmVhZCoPCgllZmZlY3RfaWQQBBgDMAE6JQohc2Fhcy5kYXRhc291cmNlLm9wZXJhdGlvbi5pbnZva2VkEANAAUgDUARYBGABgtPkkwItOgEqIigvdjEvZGF0YXNvdXJjZS1vcGVyYXRpb24tcmVjZWlwdHMvdmVyaWZ5EoUCChRMaXN0U291cmNlT3BlcmF0aW9ucxItLnNhYXMuYWNjb3VudHMudjEuTGlzdFNvdXJjZU9wZXJhdGlvbnNSZXF1ZXN0Gi4uc2Fhcy5hY2NvdW50cy52MS5MaXN0U291cmNlT3BlcmF0aW9uc1Jlc3BvbnNlIo0BwvMYRAgCEAMaD2RhdGFzb3VyY2U6cmVhZCIPZGF0YXNvdXJjZTpyZWFkKgwKBm9yZ19pZBACGAEwAToCEAFAAUgDUANYA2ABgtPkkwI/Ej0vdjEvb3JnYW5pemF0aW9ucy97b3JnX2lkfS9kYXRhc291cmNlcy97c291cmNlX2lkfS9vcGVyYXRpb25zEpMCChdEZWNsYXJlU291cmNlT3BlcmF0aW9ucxIwLnNhYXMuYWNjb3VudHMudjEuRGVjbGFyZVNvdXJjZU9wZXJhdGlvbnNSZXF1ZXN0GjEuc2Fhcy5hY2NvdW50cy52MS5EZWNsYXJlU291cmNlT3BlcmF0aW9uc1Jlc3BvbnNlIpIBwvMYRggCEAQqDAoGb3JnX2lkEAIYATABOiYKInNhYXMuZGF0YXNvdXJjZS5vcGVyYXRpb24uZGVjbGFyZWQQAkABSARQA1gDYAGC0+STAkI6ASoiPS92MS9vcmdhbml6YXRpb25zL3tvcmdfaWR9L2RhdGFzb3VyY2VzL3tzb3VyY2VfaWR9L29wZXJhdGlvbnMSrAEKD0FkZEdpdEh1YlNvdXJjZRIoLnNhYXMuYWNjb3VudHMudjEuQWRkR2l0SHViU291cmNlUmVxdWVzdBopLnNhYXMuYWNjb3VudHMudjEuQWRkR2l0SHViU291cmNlUmVzcG9uc2UiRMLzGEAIAhAEKgwKBm9yZ19pZBACGAEwATogChxzYWFzLmRhdGFzb3VyY2Uuc291cmNlLmFkZGVkEAJAAUgEUARYA2ABEpoBCglBZGRTb3VyY2USIi5zYWFzLmFjY291bnRzLnYxLkFkZFNvdXJjZVJlcXVlc3QaIy5zYWFzLmFjY291bnRzLnYxLkFkZFNvdXJjZVJlc3BvbnNlIkTC8xhACAIQBCoMCgZvcmdfaWQQAhgBMAE6IAocc2Fhcy5kYXRhc291cmNlLnNvdXJjZS5hZGRlZBACQAFIBFAEWANgARKlAQoMVXBkYXRlU291cmNlEiUuc2Fhcy5hY2NvdW50cy52MS5VcGRhdGVTb3VyY2VSZXF1ZXN0GiYuc2Fhcy5hY2NvdW50cy52MS5VcGRhdGVTb3VyY2VSZXNwb25zZSJGwvMYQggCEAQqDAoGb3JnX2lkEAIYATABOiIKHnNhYXMuZGF0YXNvdXJjZS5zb3VyY2UudXBkYXRlZBACQAFIBFACWANgARKPAQoUR2V0RGF0YXNvdXJjZUNhdGFsb2cSLS5zYWFzLmFjY291bnRzLnYxLkdldERhdGFzb3VyY2VDYXRhbG9nUmVxdWVzdBouLnNhYXMuYWNjb3VudHMudjEuR2V0RGF0YXNvdXJjZUNhdGFsb2dSZXNwb25zZSIYwvMYFAgCEAEwAToCEAFAAUgDUAJYAmABEoIBCgtMaXN0U291cmNlcxIkLnNhYXMuYWNjb3VudHMudjEuTGlzdFNvdXJjZXNSZXF1ZXN0GiUuc2Fhcy5hY2NvdW50cy52MS5MaXN0U291cmNlc1Jlc3BvbnNlIibC8xgiCAIQAyoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARJ8CglHZXRTb3VyY2USIi5zYWFzLmFjY291bnRzLnYxLkdldFNvdXJjZVJlcXVlc3QaIy5zYWFzLmFjY291bnRzLnYxLkdldFNvdXJjZVJlc3BvbnNlIibC8xgiCAIQAyoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARKeAQoKU3luY1NvdXJjZRIjLnNhYXMuYWNjb3VudHMudjEuU3luY1NvdXJjZVJlcXVlc3QaJC5zYWFzLmFjY291bnRzLnYxLlN5bmNTb3VyY2VSZXNwb25zZSJFwvMYQQgCEAQqDAoGb3JnX2lkEAIYATABOiEKHXNhYXMuZGF0YXNvdXJjZS5zb3VyY2Uuc3luY2VkEAJAAUgEUARYAmABEogBCg1HZXRTb3VyY2VTeW5jEiYuc2Fhcy5hY2NvdW50cy52MS5HZXRTb3VyY2VTeW5jUmVxdWVzdBonLnNhYXMuYWNjb3VudHMudjEuR2V0U291cmNlU3luY1Jlc3BvbnNlIibC8xgiCAIQBCoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARKlAQoMRGVsZXRlU291cmNlEiUuc2Fhcy5hY2NvdW50cy52MS5EZWxldGVTb3VyY2VSZXF1ZXN0GiYuc2Fhcy5hY2NvdW50cy52MS5EZWxldGVTb3VyY2VSZXNwb25zZSJGwvMYQggCEAQqDAoGb3JnX2lkEAIYATABOiIKHnNhYXMuZGF0YXNvdXJjZS5zb3VyY2UucmVtb3ZlZBACQAFIBFACWAJgARLEAQoTQmVnaW5HaXRIdWJBcHBTZXR1cBIsLnNhYXMuYWNjb3VudHMudjEuQmVnaW5HaXRIdWJBcHBTZXR1cFJlcXVlc3QaLS5zYWFzLmFjY291bnRzLnYxLkJlZ2luR2l0SHViQXBwU2V0dXBSZXNwb25zZSJQwvMYTAgCEAQqDAoGb3JnX2lkEAIYATABOiwKKHNhYXMuZGF0YXNvdXJjZS5naXRodWJfYXBwLnNldHVwX3N0YXJ0ZWQQAkABSARQAlgEYAESzwEKFkNvbXBsZXRlR2l0SHViQXBwU2V0dXASLy5zYWFzLmFjY291bnRzLnYxLkNvbXBsZXRlR2l0SHViQXBwU2V0dXBSZXF1ZXN0GjAuc2Fhcy5hY2NvdW50cy52MS5Db21wbGV0ZUdpdEh1YkFwcFNldHVwUmVzcG9uc2UiUsLzGE4IAhAEKgwKBm9yZ19pZBACGAEwATouCipzYWFzLmRhdGFzb3VyY2UuZ2l0aHViX2FwcC5zZXR1cF9jb21wbGV0ZWQQAkABSARQBFgDYAESzQEKGE1pZ3JhdGVHaXRIdWJTb3VyY2VUb0FwcBIxLnNhYXMuYWNjb3VudHMudjEuTWlncmF0ZUdpdEh1YlNvdXJjZVRvQXBwUmVxdWVzdBoyLnNhYXMuYWNjb3VudHMudjEuTWlncmF0ZUdpdEh1YlNvdXJjZVRvQXBwUmVzcG9uc2UiSsLzGEYIAhAEKgwKBm9yZ19pZBACGAEwATomCiJzYWFzLmRhdGFzb3VyY2UuY3JlZGVudGlhbC51cGRhdGVkEAJAAUgEUAJYA2ABEtUBChpCZWdpbkRhdGFzb3VyY2VBY2NvdW50TGluaxIzLnNhYXMuYWNjb3VudHMudjEuQmVnaW5EYXRhc291cmNlQWNjb3VudExpbmtSZXF1ZXN0GjQuc2Fhcy5hY2NvdW50cy52MS5CZWdpbkRhdGFzb3VyY2VBY2NvdW50TGlua1Jlc3BvbnNlIkzC8xhICAIQAyoMCgZvcmdfaWQQAhgBMAE6KAokc2Fhcy5kYXRhc291cmNlLmFjY291bnRfbGlua19zdGFydGVkEAJAAUgEUAJYBGABEtgBCh1Db21wbGV0ZURhdGFzb3VyY2VBY2NvdW50TGluaxI2LnNhYXMuYWNjb3VudHMudjEuQ29tcGxldGVEYXRhc291cmNlQWNjb3VudExpbmtSZXF1ZXN0Gjcuc2Fhcy5hY2NvdW50cy52MS5Db21wbGV0ZURhdGFzb3VyY2VBY2NvdW50TGlua1Jlc3BvbnNlIkbC8xhCCAIQAyoMCgZvcmdfaWQQAhgBMAE6Igoec2Fhcy5kYXRhc291cmNlLmFjY291bnRfbGlua2VkEAJAAUgEUARYA2ABErUBChxMaXN0TXlEYXRhc291cmNlQWNjb3VudExpbmtzEjUuc2Fhcy5hY2NvdW50cy52MS5MaXN0TXlEYXRhc291cmNlQWNjb3VudExpbmtzUmVxdWVzdBo2LnNhYXMuYWNjb3VudHMudjEuTGlzdE15RGF0YXNvdXJjZUFjY291bnRMaW5rc1Jlc3BvbnNlIibC8xgiCAIQAyoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARLUAQobRGVsZXRlRGF0YXNvdXJjZUFjY291bnRMaW5rEjQuc2Fhcy5hY2NvdW50cy52MS5EZWxldGVEYXRhc291cmNlQWNjb3VudExpbmtSZXF1ZXN0GjUuc2Fhcy5hY2NvdW50cy52MS5EZWxldGVEYXRhc291cmNlQWNjb3VudExpbmtSZXNwb25zZSJIwvMYRAgCEAMqDAoGb3JnX2lkEAIYATABOiQKIHNhYXMuZGF0YXNvdXJjZS5hY2NvdW50X3VubGlua2VkEAJAAUgEUAJYA2ABEqMBChZHZXREYXRhc291cmNlRGlyZWN0b3J5Ei8uc2Fhcy5hY2NvdW50cy52MS5HZXREYXRhc291cmNlRGlyZWN0b3J5UmVxdWVzdBowLnNhYXMuYWNjb3VudHMudjEuR2V0RGF0YXNvdXJjZURpcmVjdG9yeVJlc3BvbnNlIibC8xgiCAIQBCoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARK3AQoTQmluZERhdGFzb3VyY2VHcm91cBIsLnNhYXMuYWNjb3VudHMudjEuQmluZERhdGFzb3VyY2VHcm91cFJlcXVlc3QaLS5zYWFzLmFjY291bnRzLnYxLkJpbmREYXRhc291cmNlR3JvdXBSZXNwb25zZSJDwvMYPwgCEAQqDAoGb3JnX2lkEAIYATABOh8KG3NhYXMuZGF0YXNvdXJjZS5ncm91cF9ib3VuZBACQAFIBFACWANgARK/AQoVVW5iaW5kRGF0YXNvdXJjZUdyb3VwEi4uc2Fhcy5hY2NvdW50cy52MS5VbmJpbmREYXRhc291cmNlR3JvdXBSZXF1ZXN0Gi8uc2Fhcy5hY2NvdW50cy52MS5VbmJpbmREYXRhc291cmNlR3JvdXBSZXNwb25zZSJFwvMYQQgCEAQqDAoGb3JnX2lkEAIYATABOiEKHXNhYXMuZGF0YXNvdXJjZS5ncm91cF91bmJvdW5kEAJAAUgEUAJYA2ABEsABChVDbGFpbURhdGFzb3VyY2VEb21haW4SLi5zYWFzLmFjY291bnRzLnYxLkNsYWltRGF0YXNvdXJjZURvbWFpblJlcXVlc3QaLy5zYWFzLmFjY291bnRzLnYxLkNsYWltRGF0YXNvdXJjZURvbWFpblJlc3BvbnNlIkbC8xhCCAIQBCoMCgZvcmdfaWQQAhgBMAE6Igoec2Fhcy5kYXRhc291cmNlLmRvbWFpbl9jbGFpbWVkEAJAAUgEUAJYA2ABEsQBChZWZXJpZnlEYXRhc291cmNlRG9tYWluEi8uc2Fhcy5hY2NvdW50cy52MS5WZXJpZnlEYXRhc291cmNlRG9tYWluUmVxdWVzdBowLnNhYXMuYWNjb3VudHMudjEuVmVyaWZ5RGF0YXNvdXJjZURvbWFpblJlc3BvbnNlIkfC8xhDCAIQBCoMCgZvcmdfaWQQAhgBMAE6Iwofc2Fhcy5kYXRhc291cmNlLmRvbWFpbl92ZXJpZmllZBACQAFIBFACWANgARLDAQoWRGVsZXRlRGF0YXNvdXJjZURvbWFpbhIvLnNhYXMuYWNjb3VudHMudjEuRGVsZXRlRGF0YXNvdXJjZURvbWFpblJlcXVlc3QaMC5zYWFzLmFjY291bnRzLnYxLkRlbGV0ZURhdGFzb3VyY2VEb21haW5SZXNwb25zZSJGwvMYQggCEAQqDAoGb3JnX2lkEAIYATABOiIKHnNhYXMuZGF0YXNvdXJjZS5kb21haW5fcmVtb3ZlZBACQAFIBFACWANgARKgAQoVTGlzdFNvdXJjZURlbGVnYXRpb25zEi4uc2Fhcy5hY2NvdW50cy52MS5MaXN0U291cmNlRGVsZWdhdGlvbnNSZXF1ZXN0Gi8uc2Fhcy5hY2NvdW50cy52MS5MaXN0U291cmNlRGVsZWdhdGlvbnNSZXNwb25zZSImwvMYIggCEAQqDAoGb3JnX2lkEAIYATABOgIQAUABSANQAlgDYAESxwEKFlJldm9rZVNvdXJjZURlbGVnYXRpb24SLy5zYWFzLmFjY291bnRzLnYxLlJldm9rZVNvdXJjZURlbGVnYXRpb25SZXF1ZXN0GjAuc2Fhcy5hY2NvdW50cy52MS5SZXZva2VTb3VyY2VEZWxlZ2F0aW9uUmVzcG9uc2UiSsLzGEYIAhAEKgwKBm9yZ19pZBACGAEwATomCiJzYWFzLmRhdGFzb3VyY2UuZGVsZWdhdGlvbi5yZXZva2VkEAJAAUgEUAJYA2ABEusBChZEZWxlZ2F0ZVNvdXJjZVRyaWdnZXJzEi8uc2Fhcy5hY2NvdW50cy52MS5EZWxlZ2F0ZVNvdXJjZVRyaWdnZXJzUmVxdWVzdBowLnNhYXMuYWNjb3VudHMudjEuRGVsZWdhdGVTb3VyY2VUcmlnZ2Vyc1Jlc3BvbnNlIm7C8xhqCAIQBCoMCgZvcmdfaWQQAhgBMAE6Sgoic2Fhcy5kYXRhc291cmNlLmRlbGVnYXRpb24uY3JlYXRlZAoic2Fhcy5kYXRhc291cmNlLmRlbGVnYXRpb24ucmV2b2tlZBACQAFIBFACWANgARKyAQobR2V0U291cmNlVHJpZ2dlckRlY2xhcmF0aW9uEjQuc2Fhcy5hY2NvdW50cy52MS5HZXRTb3VyY2VUcmlnZ2VyRGVjbGFyYXRpb25SZXF1ZXN0GjUuc2Fhcy5hY2NvdW50cy52MS5HZXRTb3VyY2VUcmlnZ2VyRGVjbGFyYXRpb25SZXNwb25zZSImwvMYIggCEAQqDAoGb3JnX2lkEAIYATABOgIQAUABSANQAlgDYAESgwMKD1JlY29uY2lsZVNvdXJjZRIoLnNhYXMuYWNjb3VudHMudjEuUmVjb25jaWxlU291cmNlUmVxdWVzdBopLnNhYXMuYWNjb3VudHMudjEuUmVjb25jaWxlU291cmNlUmVzcG9uc2UimgLC8xhqCAIQAxoRZGF0YXNvdXJjZTppbnZva2UiEWRhdGFzb3VyY2U6aW52b2tlKg8KCXNvdXJjZV9pZBAEGAMwATohCh1zYWFzLmRhdGFzb3VyY2Uuc291cmNlLnN5bmNlZBACQANIBFACWAJgAdrzGKcBCgIIPBIDCNgEGAUiAggCKgtVTkFWQUlMQUJMRSoSUkVTT1VSQ0VfRVhIQVVTVEVEKgdBQk9SVEVEMg9zYWFzLWRhdGFzb3VyY2VKOS9zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VTZXJ2aWNlL0xvb2t1cFJlY29uY2lsZVNvdXJjZVCAIFiAIGABchgKBnNvdXJjZRIGaW52b2tlEgRyZWFkGAESvwEKFUxvb2t1cFJlY29uY2lsZVNvdXJjZRIuLnNhYXMuYWNjb3VudHMudjEuTG9va3VwUmVjb25jaWxlU291cmNlUmVxdWVzdBopLnNhYXMuYWNjb3VudHMudjEuUmVjb25jaWxlU291cmNlUmVzcG9uc2UiS8LzGEcIAhADGg9kYXRhc291cmNlOnJlYWQiD2RhdGFzb3VyY2U6cmVhZCoPCgllZmZlY3RfaWQQBBgDMAE6AhABQAFIA1ACWAJgAWIGcHJvdG8z", [file_buf_validate_validate, file_codefly_runnable_v0_options, file_google_api_annotations, file_google_protobuf_duration, file_google_protobuf_struct, file_google_protobuf_timestamp, file_saas_jobs_v1_jobs, file_saas_policy_v1_options]); /** * GitHubDatasourceConfig is the public, non-secret configuration of a GitHub @@ -4227,6 +4227,23 @@ export const DatasourceService: GenService<{ input: typeof LookupInvokeSourceOperationRequestSchema; output: typeof InvokeSourceOperationResponseSchema; }, + /** + * VerifySourceOperationReceipt conditionally reads retained output using the + * exact original Invoke request, including a nonempty declaration precondition. + * Committed means the normalized request and admitted declaration match the + * stored attempt. Unknown remains unresolved; this method never redispatches. + * Current actor, tenant and stored-source read authority apply before comparison. + * Source-scope refusals emit the existing failure audit; successful reads do not + * record another invocation or change the retained receipt. + * This is independent of the paired Runnable Lookup contract above. + * + * @generated from rpc saas.accounts.v1.DatasourceService.VerifySourceOperationReceipt + */ + verifySourceOperationReceipt: { + methodKind: "unary"; + input: typeof InvokeSourceOperationRequestSchema; + output: typeof InvokeSourceOperationResponseSchema; + }, /** * ListSourceOperations reads the currently admitted declarations under source authority. * diff --git a/module/services/frontend/code/packages/saas-sdk/package.json b/module/services/frontend/code/packages/saas-sdk/package.json index b7791c0a9..efccbf7cc 100644 --- a/module/services/frontend/code/packages/saas-sdk/package.json +++ b/module/services/frontend/code/packages/saas-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@codefly-dev/saas-sdk", - "version": "0.3.42", + "version": "0.3.43", "description": "Typed Connect client, gateway-bound facades, and audit data-graph tooling for the saas-starter public API.", "repository": { "type": "git", diff --git a/module/services/frontend/code/packages/saas-sdk/test/source-declaration.test.ts b/module/services/frontend/code/packages/saas-sdk/test/source-declaration.test.ts index 91a00e4ac..5a82a716f 100644 --- a/module/services/frontend/code/packages/saas-sdk/test/source-declaration.test.ts +++ b/module/services/frontend/code/packages/saas-sdk/test/source-declaration.test.ts @@ -1,5 +1,5 @@ import { toJson } from "@bufbuild/protobuf"; -import { createRouterTransport } from "@connectrpc/connect"; +import { Code, createRouterTransport } from "@connectrpc/connect"; import { expect, it } from "vitest"; import { accounts, DatasourceService } from "../src/index.js"; @@ -26,8 +26,47 @@ it("carries a caller's declaration precondition through the maintained source cl const expectedDeclarationDigest = `sha256:${"a".repeat(64)}`; await client.invokeSourceOperation({ ...input, expectedDeclarationDigest }); await client.invokeSourceOperation(input); - expect(observed).toEqual([ - { ...input, expectedDeclarationDigest }, - input, - ]); + expect(observed).toEqual([{ ...input, expectedDeclarationDigest }, input]); +}); + +it("verifies the exact original invocation through the maintained public client", async () => { + const input = { + orgId: "11111111-1111-4111-8111-111111111111", + sourceId: "22222222-2222-4222-8222-222222222222", + operation: "read_item", + inputJson: '{ "id": "a" }', + effectId: "retained-call", + expectedDeclarationDigest: `sha256:${"a".repeat(64)}`, + }; + const transport = createRouterTransport(({ service }) => { + service(DatasourceService, { + verifySourceOperationReceipt(request) { + expect( + toJson( + DatasourceService.method.verifySourceOperationReceipt.input, + request, + ), + ).toEqual(input); + return { + outputJson: "{}", + receipt: { + effectId: input.effectId, + status: "committed", + outputJson: "{}", + }, + }; + }, + }); + }); + const response = await accounts + .New(transport) + .datasource() + .verifySourceOperationReceipt(input); + expect(response.receipt?.status).toBe("committed"); + const oldServer = createRouterTransport(({ service }) => { + service(DatasourceService, {}); + }); + await expect( + accounts.New(oldServer).datasource().verifySourceOperationReceipt(input), + ).rejects.toMatchObject({ code: Code.Unimplemented }); }); diff --git a/module/services/frontend/code/src/gen/saas/accounts/v1/datasource_pb.ts b/module/services/frontend/code/src/gen/saas/accounts/v1/datasource_pb.ts index 846d23168..945f6739d 100644 --- a/module/services/frontend/code/src/gen/saas/accounts/v1/datasource_pb.ts +++ b/module/services/frontend/code/src/gen/saas/accounts/v1/datasource_pb.ts @@ -18,7 +18,7 @@ import type { JsonObject, Message } from "@bufbuild/protobuf"; * Describes the file saas/accounts/v1/datasource.proto. */ export const file_saas_accounts_v1_datasource: GenFile = /*@__PURE__*/ - fileDesc("CiFzYWFzL2FjY291bnRzL3YxL2RhdGFzb3VyY2UucHJvdG8SEHNhYXMuYWNjb3VudHMudjEiXgoWR2l0SHViRGF0YXNvdXJjZUNvbmZpZxIMCgRyZXBvGAEgASgJEg0KBXBhdGhzGAIgAygJEg4KBmJyYW5jaBgDIAEoCRIXCg9maWxlX2V4dGVuc2lvbnMYBCADKAki9AEKD0FwaU9BdXRoMkNvbmZpZxI2CgVncmFudBgEIAEoDjInLnNhYXMuYWNjb3VudHMudjEuQXBpT0F1dGgyQ29uZmlnLkdyYW50EhEKCXRva2VuX3VybBgBIAEoCRIRCgljbGllbnRfaWQYAiABKAkSDgoGc2NvcGVzGAMgAygJInMKBUdyYW50EhUKEUdSQU5UX1VOU1BFQ0lGSUVEEAASFwoTR1JBTlRfUkVGUkVTSF9UT0tFThABEhwKGEdSQU5UX0NMSUVOVF9DUkVERU5USUFMUxACEhwKGEdSQU5UX0FVVEhPUklaQVRJT05fQ09ERRADIuoBChNBcGlEYXRhc291cmNlQ29uZmlnEhAKCGJhc2VfdXJsGAEgASgJEhUKDXJlc291cmNlX3BhdGgYAiABKAkSPAoPY3JlZGVudGlhbF9raW5kGAMgASgOMiMuc2Fhcy5hY2NvdW50cy52MS5BcGlDcmVkZW50aWFsS2luZBIZChFjcmVkZW50aWFsX2hlYWRlchgEIAEoCRIeChZjcmVkZW50aWFsX3F1ZXJ5X3BhcmFtGAUgASgJEjEKBm9hdXRoMhgGIAEoCzIhLnNhYXMuYWNjb3VudHMudjEuQXBpT0F1dGgyQ29uZmlnIkEKF0NyYXdsZXJEYXRhc291cmNlQ29uZmlnEhMKC3NpdGVtYXBfdXJsGAEgASgJEhEKCW1heF9wYWdlcxgCIAEoDSKrAgoWVXBsb2FkRGF0YXNvdXJjZUNvbmZpZxIQCghlbmRwb2ludBgBIAEoCRIOCgZyZWdpb24YAiABKAkSDgoGYnVja2V0GAMgASgJEg4KBnByZWZpeBgEIAEoCRIVCg1hY2Nlc3Nfa2V5X2lkGAUgASgJEhMKC21heF9vYmplY3RzGAYgASgNEisKEGluY2x1ZGVfcGF0dGVybnMYByADKAlCEbpIDpIBCxAQIgdyBRABGIACEisKEGV4Y2x1ZGVfcGF0dGVybnMYCCADKAlCEbpIDpIBCxAQIgdyBRABGIACEkkKD2ZpbGVfZXh0ZW5zaW9ucxgJIAMoCUIwukgtkgEqECAiJnIkEAIYITIeXlwuW0EtWmEtejAtOV1bQS1aYS16MC05Ll8tXSokIoMHCgpEYXRhc291cmNlEgoKAmlkGAEgASgJEg4KBm9yZ19pZBgCIAEoCRI2Cghwcm92aWRlchgDIAEoDjIkLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVByb3ZpZGVyEjgKBmdpdGh1YhgFIAEoCzIoLnNhYXMuYWNjb3VudHMudjEuR2l0SHViRGF0YXNvdXJjZUNvbmZpZxIyCgZzdGF0dXMYBiABKA4yIi5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VTdGF0dXMSGgoSd2ViaG9va19jb25maWd1cmVkGAcgASgIEi4KCmNyZWF0ZWRfYXQYCCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjIKDmxhc3Rfc3luY2VkX2F0GAogASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIyCgNhcGkYCyABKAsyJS5zYWFzLmFjY291bnRzLnYxLkFwaURhdGFzb3VyY2VDb25maWcSOgoHY3Jhd2xlchgMIAEoCzIpLnNhYXMuYWNjb3VudHMudjEuQ3Jhd2xlckRhdGFzb3VyY2VDb25maWcSOAoGdXBsb2FkGA0gASgLMiguc2Fhcy5hY2NvdW50cy52MS5VcGxvYWREYXRhc291cmNlQ29uZmlnEhgKEGJvdW5kYXJ5X25vZGVfaWQYDiABKAkSNAoQbGFzdF9pbmdlc3RlZF9hdBgPIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASHAoUbGFzdF9pbmdlc3RlZF9jb21taXQYECABKAkSFQoNc3RhdHVzX3JlYXNvbhgRIAEoCRIWCg5ib3VuZGFyeV9sYWJlbBgSIAEoCRISCgpjb25mb3JtYW50GBMgASgIEhcKD2NvbmZvcm1hbmNlX2dhcBgUIAEoCRI/Cg1saXZlX2RlbGl2ZXJ5GBUgASgOMiguc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlTGl2ZURlbGl2ZXJ5EjUKEnJlY29uY2lsZV9pbnRlcnZhbBgWIAEoCzIZLmdvb2dsZS5wcm90b2J1Zi5EdXJhdGlvbkoECAQQBVIRdGFyZ2V0X2NvbGxlY3Rpb24isgMKFkFkZEdpdEh1YlNvdXJjZVJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARI7CgRyZXBvGAIgASgJQi26SCpyKBADGP8BMiFeW0EtWmEtejAtOV8uLV0rL1tBLVphLXowLTlfLi1dKyQSIAoFcGF0aHMYAyADKAlCEbpIDpIBCxBAIgdyBRABGIAEEhgKBmJyYW5jaBgEIAEoCUIIukgFcgMY/wESJAoQYm91bmRhcnlfbm9kZV9pZBgIIAEoCUIIukgFcgOwAQFIABImChBjb2xsZWN0aW9uX2xhYmVsGAkgASgJQgq6SAdyBRABGP8BSAASHgoMYWNjZXNzX3Rva2VuGAYgASgJQgi6SAVyAxiACBIgCg53ZWJob29rX3NlY3JldBgHIAEoCUIIukgFcgMYgAgSSQoPZmlsZV9leHRlbnNpb25zGAogAygJQjC6SC2SASoQICImciQQAhghMh5eXC5bQS1aYS16MC05XVtBLVphLXowLTkuXy1dKiRCEQoIYm91bmRhcnkSBbpIAggBSgQIBRAGUhF0YXJnZXRfY29sbGVjdGlvbiJLChdBZGRHaXRIdWJTb3VyY2VSZXNwb25zZRIwCgpkYXRhc291cmNlGAEgASgLMhwuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlIsgEChBBZGRTb3VyY2VSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESQgoIcHJvdmlkZXIYAiABKA4yJC5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VQcm92aWRlckIKukgHggEEEAEgABI6CgZnaXRodWIYBCABKAsyKC5zYWFzLmFjY291bnRzLnYxLkdpdEh1YkRhdGFzb3VyY2VDb25maWdIABI0CgNhcGkYBSABKAsyJS5zYWFzLmFjY291bnRzLnYxLkFwaURhdGFzb3VyY2VDb25maWdIABI8CgdjcmF3bGVyGAggASgLMikuc2Fhcy5hY2NvdW50cy52MS5DcmF3bGVyRGF0YXNvdXJjZUNvbmZpZ0gAEjoKBnVwbG9hZBgJIAEoCzIoLnNhYXMuYWNjb3VudHMudjEuVXBsb2FkRGF0YXNvdXJjZUNvbmZpZ0gAEiQKEGJvdW5kYXJ5X25vZGVfaWQYDCABKAlCCLpIBXIDsAEBSAESJgoQY29sbGVjdGlvbl9sYWJlbBgNIAEoCUIKukgHcgUQARj/AUgBEhwKCmNyZWRlbnRpYWwYBiABKAlCCLpIBXIDGIAgEiAKDndlYmhvb2tfc2VjcmV0GAcgASgJQgi6SAVyAxiACBImChRvYXV0aDJfY2xpZW50X3NlY3JldBgKIAEoCUIIukgFcgMYgAhCCAoGY29uZmlnQhEKCGJvdW5kYXJ5EgW6SAIIAUoECAMQBFIRdGFyZ2V0X2NvbGxlY3Rpb24iRQoRQWRkU291cmNlUmVzcG9uc2USMAoKZGF0YXNvdXJjZRgBIAEoCzIcLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZSLkAgoTVXBkYXRlU291cmNlUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhQKAmlkGAIgASgJQgi6SAVyA7ABARIYCgZicmFuY2gYAyABKAlCCLpIBXIDGP8BEiAKBXBhdGhzGAQgAygJQhG6SA6SAQsQQCIHcgUQARiABBJJCg9maWxlX2V4dGVuc2lvbnMYBSADKAlCMLpILZIBKhAgIiZyJBACGCEyHl5cLltBLVphLXowLTldW0EtWmEtejAtOS5fLV0qJBI8CghleHBlY3RlZBgGIAEoCzIiLnNhYXMuYWNjb3VudHMudjEuU291cmNlRmV0Y2hTY29wZUIGukgDyAEBEisKEGluY2x1ZGVfcGF0dGVybnMYByADKAlCEbpIDpIBCxAQIgdyBRABGIACEisKEGV4Y2x1ZGVfcGF0dGVybnMYCCADKAlCEbpIDpIBCxAQIgdyBRABGIACIs8BChBTb3VyY2VGZXRjaFNjb3BlEhgKBmJyYW5jaBgBIAEoCUIIukgFcgMY/wESIAoFcGF0aHMYAiADKAlCEbpIDpIBCxBAIgdyBRABGIAEEikKD2ZpbGVfZXh0ZW5zaW9ucxgDIAMoCUIQukgNkgEKECAiBnIEEAEYIRIpChBpbmNsdWRlX3BhdHRlcm5zGAQgAygJQg+6SAySAQkQECIFcgMYgAISKQoQZXhjbHVkZV9wYXR0ZXJucxgFIAMoCUIPukgMkgEJEBAiBXIDGIACIlgKFFVwZGF0ZVNvdXJjZVJlc3BvbnNlEjAKCmRhdGFzb3VyY2UYASABKAsyHC5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2USDgoGam9iX2lkGAIgASgJIloKFURhdGFzb3VyY2VDb25maWdGaWVsZBILCgNrZXkYASABKAkSFAoMZGlzcGxheV9uYW1lGAIgASgJEgwKBGhlbHAYAyABKAkSEAoIcmVxdWlyZWQYBCABKAgixAEKGURhdGFzb3VyY2VDb25uZWN0b3JCdWRnZXQSGgoSbWF4X2l0ZW1zX3Blcl9jYWxsGAEgASgNEhoKEm1heF9ieXRlc19wZXJfY2FsbBgCIAEoAxIWCg5tYXhfaXRlbV9ieXRlcxgDIAEoAxIdChVvcGVyYXRpb25zX3Blcl93aW5kb3cYBCABKA0SFgoOd2luZG93X3NlY29uZHMYBSABKAMSIAoYYmFja2dyb3VuZF9zaGFyZV9wZXJjZW50GAYgASgNIqoGChxEYXRhc291cmNlUHJvdmlkZXJEZXNjcmlwdG9yEjYKCHByb3ZpZGVyGAEgASgOMiQuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlUHJvdmlkZXISFAoMZGlzcGxheV9uYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEj4KDWNvbmZpZ19maWVsZHMYBCADKAsyJy5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VDb25maWdGaWVsZBIYChBzdXBwb3J0c193ZWJob29rGAUgASgIEkcKGnN1cHBvcnRlZF9jcmVkZW50aWFsX2tpbmRzGAYgAygOMiMuc2Fhcy5hY2NvdW50cy52MS5BcGlDcmVkZW50aWFsS2luZBIRCgljb25uZWN0b3IYByABKAkSQQoJaW50ZXJmYWNlGAggASgOMi4uc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlQ29ubmVjdG9ySW50ZXJmYWNlEkQKEGNyZWRlbnRpYWxfbW9kZXMYCSADKA4yKi5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VDcmVkZW50aWFsTW9kZRI/Cg1yZWFkZXJzX21vZGVsGAogASgOMiguc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlUmVhZGVyc01vZGVsEjsKBmJ1ZGdldBgLIAEoCzIrLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZUNvbm5lY3RvckJ1ZGdldBISCgpjb25mb3JtYW50GAwgASgIEhcKD2NvbmZvcm1hbmNlX2dhcBgNIAEoCRIbChNhY2NlcHRzX25ld19zb3VyY2VzGA4gASgIEiAKGGxpdmVfZGVsaXZlcnlfY29uZmlndXJlZBgPIAEoCBIaChJhY2NlcHRzX29wZXJhdGlvbnMYECABKAgSFgoOb3BlcmF0aW9uc19nYXAYESABKAkSGwoTb2F1dGhfYXV0aG9yaXplX3VybBgSIAEoCRIXCg9vYXV0aF90b2tlbl91cmwYEyABKAkSFAoMb2F1dGhfc2NvcGVzGBQgAygJIh0KG0dldERhdGFzb3VyY2VDYXRhbG9nUmVxdWVzdCJhChxHZXREYXRhc291cmNlQ2F0YWxvZ1Jlc3BvbnNlEkEKCXByb3ZpZGVycxgBIAMoCzIuLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVByb3ZpZGVyRGVzY3JpcHRvciIuChJMaXN0U291cmNlc1JlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABASJIChNMaXN0U291cmNlc1Jlc3BvbnNlEjEKC2RhdGFzb3VyY2VzGAEgAygLMhwuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlIkIKEEdldFNvdXJjZVJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIUCgJpZBgCIAEoCUIIukgFcgOwAQEiRQoRR2V0U291cmNlUmVzcG9uc2USMAoKZGF0YXNvdXJjZRgBIAEoCzIcLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZSJ2ChFTeW5jU291cmNlUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhQKAmlkGAIgASgJQgi6SAVyA7ABARIeCgxhY2Nlc3NfdG9rZW4YAyABKAlCCLpIBXIDGIAgEhEKCXJlY29ubmVjdBgEIAEoCCIkChJTeW5jU291cmNlUmVzcG9uc2USDgoGam9iX2lkGAEgASgJImoKFEdldFNvdXJjZVN5bmNSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGwoJc291cmNlX2lkGAIgASgJQgi6SAVyA7ABARIbCgZqb2JfaWQYAyABKAlCC7pICNgBAXIDsAEBIpUBChJTb3VyY2VTeW5jRGVsaXZlcnkSGAoGam9iX2lkGAEgASgJQgi6SAVyA7ABARItCgVzdGF0ZRgCIAEoDjIWLnNhYXMuam9icy52MS5Kb2JTdGF0ZUIGukgDyAEBEjYKCWV4ZWN1dGlvbhgDIAEoCzIjLnNhYXMuam9icy52MS5Kb2JFeGVjdXRpb25SZWZlcmVuY2UiiwEKEVNvdXJjZVN5bmNDaGFuZ2VzEg0KBWZpbGVzGAEgASgNEg0KBWFkZGVkGAIgASgNEhAKCG1vZGlmaWVkGAMgASgNEg8KB2RlbGV0ZWQYBCABKA0SEwoLc3BsaXRfa25vd24YBSABKAgSEAoIc25hcHNob3QYBiABKAgSDgoGY29tbWl0GAcgASgJIq0BChFTb3VyY2VTeW5jRmFpbHVyZRI5CgZyZWFzb24YASABKA4yKS5zYWFzLmFjY291bnRzLnYxLlNvdXJjZVN5bmNGYWlsdXJlUmVhc29uEgwKBGNvZGUYAiABKAkSDwoHbWVzc2FnZRgDIAEoCRIQCghyZXRyeWluZxgEIAEoCBIsCghyZXRyeV9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAihAQKElNvdXJjZVN5bmNQcm9ncmVzcxIwCgVwaGFzZRgBIAEoDjIhLnNhYXMuYWNjb3VudHMudjEuU291cmNlU3luY1BoYXNlEjQKB3RyaWdnZXIYAiABKA4yIy5zYWFzLmFjY291bnRzLnYxLlNvdXJjZVN5bmNUcmlnZ2VyEi0KCXF1ZXVlZF9hdBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLwoLZmV0Y2hpbmdfYXQYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi8KC2NvbXBpbGVkX2F0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIxCg1oYW5kZWRfb2ZmX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIvCgtmaW5pc2hlZF9hdBgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASNAoHY2hhbmdlcxgIIAEoCzIjLnNhYXMuYWNjb3VudHMudjEuU291cmNlU3luY0NoYW5nZXMSNAoHZmFpbHVyZRgJIAEoCzIjLnNhYXMuYWNjb3VudHMudjEuU291cmNlU3luY0ZhaWx1cmUSDwoHYXR0ZW1wdBgKIAEoDRIUCgxtYXhfYXR0ZW1wdHMYCyABKA0i0gEKFUdldFNvdXJjZVN5bmNSZXNwb25zZRIYCgZqb2JfaWQYASABKAlCCLpIBXIDsAEBEi0KBXN0YXRlGAIgASgOMhYuc2Fhcy5qb2JzLnYxLkpvYlN0YXRlQga6SAPIAQESOAoKZGVsaXZlcmllcxgDIAMoCzIkLnNhYXMuYWNjb3VudHMudjEuU291cmNlU3luY0RlbGl2ZXJ5EjYKCHByb2dyZXNzGAQgASgLMiQuc2Fhcy5hY2NvdW50cy52MS5Tb3VyY2VTeW5jUHJvZ3Jlc3MiRQoTRGVsZXRlU291cmNlUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhQKAmlkGAIgASgJQgi6SAVyA7ABASIWChREZWxldGVTb3VyY2VSZXNwb25zZSJWChNHaXRIdWJBcHBSZXBvc2l0b3J5EgwKBHJlcG8YASABKAkSFgoOZGVmYXVsdF9icmFuY2gYAiABKAkSGQoRYWxyZWFkeV9jb25uZWN0ZWQYAyABKAgiNgoaQmVnaW5HaXRIdWJBcHBTZXR1cFJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABASJxChtCZWdpbkdpdEh1YkFwcFNldHVwUmVzcG9uc2USEwoLaW5zdGFsbF91cmwYASABKAkSDQoFc3RhdGUYAiABKAkSLgoKZXhwaXJlc19hdBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAinAEKHUNvbXBsZXRlR2l0SHViQXBwU2V0dXBSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGQoFc3RhdGUYAiABKAlCCrpIB3IFEAEY/wESLAoPaW5zdGFsbGF0aW9uX2lkGAMgASgJQhO6SBByDhABGCAyCF5bMC05XSskEhgKBGNvZGUYBCABKAlCCrpIB3IFEAEY/wEidgoeQ29tcGxldGVHaXRIdWJBcHBTZXR1cFJlc3BvbnNlEhcKD2luc3RhbGxhdGlvbl9pZBgBIAEoCRI7CgxyZXBvc2l0b3JpZXMYAiADKAsyJS5zYWFzLmFjY291bnRzLnYxLkdpdEh1YkFwcFJlcG9zaXRvcnkiUQofTWlncmF0ZUdpdEh1YlNvdXJjZVRvQXBwUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhQKAmlkGAIgASgJQgi6SAVyA7ABASJUCiBNaWdyYXRlR2l0SHViU291cmNlVG9BcHBSZXNwb25zZRIwCgpkYXRhc291cmNlGAEgASgLMhwuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlIsQBChVEYXRhc291cmNlQWNjb3VudExpbmsSCgoCaWQYASABKAkSDgoGb3JnX2lkGAIgASgJEg8KB3VzZXJfaWQYAyABKAkSEQoJY29ubmVjdG9yGAQgASgJEhsKE3Byb3ZpZGVyX2FjY291bnRfaWQYBSABKAkSHgoWcHJvdmlkZXJfYWNjb3VudF9sb2dpbhgGIAEoCRIuCgpjcmVhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCK3AQoWRGF0YXNvdXJjZUdyb3VwQmluZGluZxIKCgJpZBgBIAEoCRIOCgZvcmdfaWQYAiABKAkSEQoJY29ubmVjdG9yGAMgASgJEhkKEXByb3ZpZGVyX2dyb3VwX2lkGAQgASgJEg8KB3RlYW1faWQYBSABKAkSEgoKY3JlYXRlZF9ieRgGIAEoCRIuCgpjcmVhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKUAgoYRGF0YXNvdXJjZVZlcmlmaWVkRG9tYWluEgoKAmlkGAEgASgJEg4KBm9yZ19pZBgCIAEoCRIOCgZkb21haW4YAyABKAkSOAoGc3RhdHVzGAQgASgOMiguc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlRG9tYWluU3RhdHVzEhcKD3R4dF9yZWNvcmRfbmFtZRgFIAEoCRIYChB0eHRfcmVjb3JkX3ZhbHVlGAYgASgJEi8KC3ZlcmlmaWVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgpjcmVhdGVkX2F0GAggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCIzChdEYXRhc291cmNlRGlyZWN0b3J5VGVhbRIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJIqYBCiFCZWdpbkRhdGFzb3VyY2VBY2NvdW50TGlua1JlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARI0Cgljb25uZWN0b3IYAiABKAlCIbpIHnIcEAIYIDIWXlthLXpdW2EtejAtOS1dezEsMzF9JBIeCgxyZWRpcmVjdF91cmkYAyABKAlCCLpIBXIDGIAQEhEKCXNvdXJjZV9pZBgEIAEoCSJ6CiJCZWdpbkRhdGFzb3VyY2VBY2NvdW50TGlua1Jlc3BvbnNlEhUKDWF1dGhvcml6ZV91cmwYASABKAkSDQoFc3RhdGUYAiABKAkSLgoKZXhwaXJlc19hdBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAidQokQ29tcGxldGVEYXRhc291cmNlQWNjb3VudExpbmtSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGQoFc3RhdGUYAiABKAlCCrpIB3IFEAEYgEASGAoEY29kZRgDIAEoCUIKukgHcgUQARj/ASJeCiVDb21wbGV0ZURhdGFzb3VyY2VBY2NvdW50TGlua1Jlc3BvbnNlEjUKBGxpbmsYASABKAsyJy5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VBY2NvdW50TGluayI/CiNMaXN0TXlEYXRhc291cmNlQWNjb3VudExpbmtzUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBIl4KJExpc3RNeURhdGFzb3VyY2VBY2NvdW50TGlua3NSZXNwb25zZRI2CgVsaW5rcxgBIAMoCzInLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZUFjY291bnRMaW5rIlQKIkRlbGV0ZURhdGFzb3VyY2VBY2NvdW50TGlua1JlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIUCgJpZBgCIAEoCUIIukgFcgOwAQEiJQojRGVsZXRlRGF0YXNvdXJjZUFjY291bnRMaW5rUmVzcG9uc2UiOQodR2V0RGF0YXNvdXJjZURpcmVjdG9yeVJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABASKLAgoeR2V0RGF0YXNvdXJjZURpcmVjdG9yeVJlc3BvbnNlEjYKBWxpbmtzGAEgAygLMicuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlQWNjb3VudExpbmsSOgoIYmluZGluZ3MYAiADKAsyKC5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VHcm91cEJpbmRpbmcSOwoHZG9tYWlucxgDIAMoCzIqLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVZlcmlmaWVkRG9tYWluEjgKBXRlYW1zGAQgAygLMikuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlRGlyZWN0b3J5VGVhbSKuAQoaQmluZERhdGFzb3VyY2VHcm91cFJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARI0Cgljb25uZWN0b3IYAiABKAlCIbpIHnIcEAIYIDIWXlthLXpdW2EtejAtOS1dezEsMzF9JBIlChFwcm92aWRlcl9ncm91cF9pZBgDIAEoCUIKukgHcgUQARj/ARIZCgd0ZWFtX2lkGAQgASgJQgi6SAVyA7ABASJYChtCaW5kRGF0YXNvdXJjZUdyb3VwUmVzcG9uc2USOQoHYmluZGluZxgBIAEoCzIoLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZUdyb3VwQmluZGluZyJOChxVbmJpbmREYXRhc291cmNlR3JvdXBSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESFAoCaWQYAiABKAlCCLpIBXIDsAEBIh8KHVVuYmluZERhdGFzb3VyY2VHcm91cFJlc3BvbnNlIlQKHENsYWltRGF0YXNvdXJjZURvbWFpblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIaCgZkb21haW4YAiABKAlCCrpIB3IFEAMY/QEiWwodQ2xhaW1EYXRhc291cmNlRG9tYWluUmVzcG9uc2USOgoGZG9tYWluGAEgASgLMiouc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlVmVyaWZpZWREb21haW4iTwodVmVyaWZ5RGF0YXNvdXJjZURvbWFpblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIUCgJpZBgCIAEoCUIIukgFcgOwAQEiXAoeVmVyaWZ5RGF0YXNvdXJjZURvbWFpblJlc3BvbnNlEjoKBmRvbWFpbhgBIAEoCzIqLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVZlcmlmaWVkRG9tYWluIk8KHURlbGV0ZURhdGFzb3VyY2VEb21haW5SZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESFAoCaWQYAiABKAlCCLpIBXIDsAEBIiAKHkRlbGV0ZURhdGFzb3VyY2VEb21haW5SZXNwb25zZSKeAgoQU291cmNlRGVsZWdhdGlvbhIKCgJpZBgBIAEoCRIRCglzb3VyY2VfaWQYAiABKAkSFAoMcHJpbmNpcGFsX2lkGAMgASgJEg4KBm1vZHVsZRgEIAEoCRIPCgdiaW5kaW5nGAUgASgJEi4KCmNyZWF0ZWRfYXQYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnJldm9rZWRfYXQYByABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEkAKCnJldm9jYXRpb24YCCABKA4yLC5zYWFzLmFjY291bnRzLnYxLlNvdXJjZURlbGVnYXRpb25SZXZvY2F0aW9uEhIKCnJldm9rZWRfYnkYCSABKAkigQEKHExpc3RTb3VyY2VEZWxlZ2F0aW9uc1JlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIgCglzb3VyY2VfaWQYAiABKAlCCLpIBXIDsAEBSACIAQESFwoPaW5jbHVkZV9yZXZva2VkGAMgASgIQgwKCl9zb3VyY2VfaWQiWAodTGlzdFNvdXJjZURlbGVnYXRpb25zUmVzcG9uc2USNwoLZGVsZWdhdGlvbnMYASADKAsyIi5zYWFzLmFjY291bnRzLnYxLlNvdXJjZURlbGVnYXRpb24iTwodUmV2b2tlU291cmNlRGVsZWdhdGlvblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIUCgJpZBgCIAEoCUIIukgFcgOwAQEiWAoeUmV2b2tlU291cmNlRGVsZWdhdGlvblJlc3BvbnNlEjYKCmRlbGVnYXRpb24YASABKAsyIi5zYWFzLmFjY291bnRzLnYxLlNvdXJjZURlbGVnYXRpb24iMgoPVHJpZ2dlcklkZW50aXR5Eg4KBnNvdXJjZRgBIAEoCRIPCgdiaW5kaW5nGAIgASgJIoQBChdEZWNsYXJlZFRyaWdnZXJTY2hlZHVsZRIMCgRjcm9uGAEgASgJEhAKCHRpbWV6b25lGAIgASgJEhIKCm1pc3NlZF9ydW4YAyABKAkSDwoHb3ZlcmxhcBgEIAEoCRIQCghkZWFkbGluZRgFIAEoAxISCgppbnB1dF9qc29uGAYgASgJIucCChpUcmlnZ2VyRGVsZWdhdGlvblJlZmVyZW5jZRIRCglyZWZlcmVuY2UYASABKAkSFQoNZGVsZWdhdGlvbl9pZBgCIAEoCRIOCgZtb2R1bGUYAyABKAkSDwoHYmluZGluZxgEIAEoCRIQCghhdWRpZW5jZRgFIAEoCRIOCgZ0ZW5hbnQYBiABKAkSGgoSb3duZXJfcHJpbmNpcGFsX2lkGAcgASgJEg4KBnNjb3BlcxgIIAMoCRIOCgZhY3RpdmUYCSABKAgSLgoKY3JlYXRlZF9hdBgKIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKcmV2b2tlZF9hdBgLIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASQAoKcmV2b2NhdGlvbhgMIAEoDjIsLnNhYXMuYWNjb3VudHMudjEuU291cmNlRGVsZWdhdGlvblJldm9jYXRpb24ivgEKE1RyaWdnZXJDaGlsZENvbnRleHQSFQoNZGVsZWdhdGlvbl9pZBgBIAEoCRIOCgZtb2R1bGUYAiABKAkSDwoHYmluZGluZxgDIAEoCRIQCghhdWRpZW5jZRgEIAEoCRIOCgZzY29wZXMYBSADKAkSDgoGbG9va3VwGAYgASgIEi0KCW1pbnRlZF9hdBgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASDgoGcmVjb3JkGAggASgJIs0CChhTb3VyY2VUcmlnZ2VyRGVjbGFyYXRpb24SDwoHcmVsZWFzZRgBIAEoCRIRCglvcGVyYXRpb24YAiABKAkSOwoIc2NoZWR1bGUYAyABKAsyKS5zYWFzLmFjY291bnRzLnYxLkRlY2xhcmVkVHJpZ2dlclNjaGVkdWxlEjMKCGlkZW50aXR5GAQgASgLMiEuc2Fhcy5hY2NvdW50cy52MS5UcmlnZ2VySWRlbnRpdHkSDgoGZGlnZXN0GAUgASgJEjYKEmxhc3Rfb2NjdXJyZW5jZV9hdBgGIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASNgoSbmV4dF9vY2N1cnJlbmNlX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIbChN1bmRlY2xhcmFibGVfcmVhc29uGAggASgJIlYKHURlbGVnYXRlU291cmNlVHJpZ2dlcnNSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGwoJc291cmNlX2lkGAIgASgJQgi6SAVyA7ABASJjCh5EZWxlZ2F0ZVNvdXJjZVRyaWdnZXJzUmVzcG9uc2USQQoLZGVsZWdhdGlvbnMYASADKAsyLC5zYWFzLmFjY291bnRzLnYxLlRyaWdnZXJEZWxlZ2F0aW9uUmVmZXJlbmNlIlsKIkdldFNvdXJjZVRyaWdnZXJEZWNsYXJhdGlvblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIbCglzb3VyY2VfaWQYAiABKAlCCLpIBXIDsAEBIuIBCiNHZXRTb3VyY2VUcmlnZ2VyRGVjbGFyYXRpb25SZXNwb25zZRI/CgtkZWNsYXJhdGlvbhgBIAEoCzIqLnNhYXMuYWNjb3VudHMudjEuU291cmNlVHJpZ2dlckRlY2xhcmF0aW9uEkEKC2RlbGVnYXRpb25zGAIgAygLMiwuc2Fhcy5hY2NvdW50cy52MS5UcmlnZ2VyRGVsZWdhdGlvblJlZmVyZW5jZRI3CghjaGlsZHJlbhgDIAMoCzIlLnNhYXMuYWNjb3VudHMudjEuVHJpZ2dlckNoaWxkQ29udGV4dCJuChZSZWNvbmNpbGVTb3VyY2VSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGwoJc291cmNlX2lkGAIgASgJQgi6SAVyA7ABARIdCgllZmZlY3RfaWQYAyABKAlCCrpIB3IFEAEYgAEiTAoXUmVjb25jaWxlU291cmNlUmVzcG9uc2USEQoJZWZmZWN0X2lkGAEgASgJEg4KBnN0YXR1cxgCIAEoCRIOCgZqb2JfaWQYAyABKAkiPQocTG9va3VwUmVjb25jaWxlU291cmNlUmVxdWVzdBIdCgllZmZlY3RfaWQYASABKAlCCrpIB3IFEAEYgAEi8QIKD1NvdXJjZU9wZXJhdGlvbhIMCgRuYW1lGAEgASgJEhMKC2Rlc2NyaXB0aW9uGAIgASgJEg4KBm1ldGhvZBgDIAEoCRIMCgRwYXRoGAQgASgJEg0KBXF1ZXJ5GAUgAygJEi0KDGlucHV0X3NjaGVtYRgGIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSLgoNb3V0cHV0X3NjaGVtYRgHIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSOAoGZWZmZWN0GAggASgOMiguc2Fhcy5hY2NvdW50cy52MS5Tb3VyY2VPcGVyYXRpb24uRWZmZWN0EhgKEG1heF9vdXRwdXRfYnl0ZXMYCSABKA0SDgoGZGlnZXN0GAogASgJIksKBkVmZmVjdBIWChJFRkZFQ1RfVU5TUEVDSUZJRUQQABIUChBFRkZFQ1RfUkVBRF9PTkxZEAESEwoPRUZGRUNUX01VVEFUSU9OEAIijgEKHkRlY2xhcmVTb3VyY2VPcGVyYXRpb25zUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhsKCXNvdXJjZV9pZBgCIAEoCUIIukgFcgOwAQESNQoKb3BlcmF0aW9ucxgDIAMoCzIhLnNhYXMuYWNjb3VudHMudjEuU291cmNlT3BlcmF0aW9uIlgKH0RlY2xhcmVTb3VyY2VPcGVyYXRpb25zUmVzcG9uc2USNQoKb3BlcmF0aW9ucxgBIAMoCzIhLnNhYXMuYWNjb3VudHMudjEuU291cmNlT3BlcmF0aW9uIlQKG0xpc3RTb3VyY2VPcGVyYXRpb25zUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhsKCXNvdXJjZV9pZBgCIAEoCUIIukgFcgOwAQEiVQocTGlzdFNvdXJjZU9wZXJhdGlvbnNSZXNwb25zZRI1CgpvcGVyYXRpb25zGAEgAygLMiEuc2Fhcy5hY2NvdW50cy52MS5Tb3VyY2VPcGVyYXRpb24irgIKHEludm9rZVNvdXJjZU9wZXJhdGlvblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIbCglzb3VyY2VfaWQYAiABKAlCCLpIBXIDsAEBEjAKCW9wZXJhdGlvbhgDIAEoCUIdukgachgyFl5bYS16XVthLXowLTlfXXswLDYzfSQSHQoKaW5wdXRfanNvbhgEIAEoCUIJukgGcgQogIAEEhsKCWVmZmVjdF9pZBgFIAEoCUIIukgFcgMogAESSQobZXhwZWN0ZWRfZGVjbGFyYXRpb25fZGlnZXN0GAYgASgJQh+6SBxyGjIYXihzaGEyNTY6WzAtOWEtZl17NjR9KT8kSACIAQFCHgocX2V4cGVjdGVkX2RlY2xhcmF0aW9uX2RpZ2VzdCJ/ChZTb3VyY2VPcGVyYXRpb25SZWNlaXB0EhEKCWVmZmVjdF9pZBgBIAEoCRIUCgxjb21taXR0ZWRfYXQYAiABKAkSDgoGc3RhdHVzGAMgASgJEhcKD3Byb3ZpZGVyX3N0YXR1cxgEIAEoDRITCgtvdXRwdXRfanNvbhgFIAEoCSJvCh1JbnZva2VTb3VyY2VPcGVyYXRpb25SZXNwb25zZRITCgtvdXRwdXRfanNvbhgBIAEoCRI5CgdyZWNlaXB0GAIgASgLMiguc2Fhcy5hY2NvdW50cy52MS5Tb3VyY2VPcGVyYXRpb25SZWNlaXB0IkMKIkxvb2t1cEludm9rZVNvdXJjZU9wZXJhdGlvblJlcXVlc3QSHQoJZWZmZWN0X2lkGAEgASgJQgq6SAdyBRABKIABInkKI1BydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGwoJc291cmNlX2lkGAIgASgJQgi6SAVyA7ABARIbCgllZmZlY3RfaWQYAyABKAlCCLpIBXIDKIABIlsKJFBydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXNwb25zZRIRCgllZmZlY3RfaWQYASABKAkSEAoIcmVjZWlwdHMYAiABKAMSDgoGc3RhdHVzGAMgASgJIkoKKUxvb2t1cFBydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXF1ZXN0Eh0KCWVmZmVjdF9pZBgBIAEoCUIKukgHcgUQASiAASq3AQoSRGF0YXNvdXJjZVByb3ZpZGVyEiMKH0RBVEFTT1VSQ0VfUFJPVklERVJfVU5TUEVDSUZJRUQQABIeChpEQVRBU09VUkNFX1BST1ZJREVSX0dJVEhVQhABEhsKF0RBVEFTT1VSQ0VfUFJPVklERVJfQVBJEAISHwobREFUQVNPVVJDRV9QUk9WSURFUl9DUkFXTEVSEAMSHgoaREFUQVNPVVJDRV9QUk9WSURFUl9VUExPQUQQBCqRAQoQRGF0YXNvdXJjZVN0YXR1cxIhCh1EQVRBU09VUkNFX1NUQVRVU19VTlNQRUNJRklFRBAAEhwKGERBVEFTT1VSQ0VfU1RBVFVTX0FDVElWRRABEhwKGERBVEFTT1VSQ0VfU1RBVFVTX1BBVVNFRBACEh4KGkRBVEFTT1VSQ0VfU1RBVFVTX0RFR1JBREVEEAMqvAEKFkRhdGFzb3VyY2VMaXZlRGVsaXZlcnkSKAokREFUQVNPVVJDRV9MSVZFX0RFTElWRVJZX1VOU1BFQ0lGSUVEEAASIQodREFUQVNPVVJDRV9MSVZFX0RFTElWRVJZX05PTkUQARIrCidEQVRBU09VUkNFX0xJVkVfREVMSVZFUllfU09VUkNFX1dFQkhPT0sQAhIoCiREQVRBU09VUkNFX0xJVkVfREVMSVZFUllfQVBQX1dFQkhPT0sQAyrWAQoRQXBpQ3JlZGVudGlhbEtpbmQSIwofQVBJX0NSRURFTlRJQUxfS0lORF9VTlNQRUNJRklFRBAAEh4KGkFQSV9DUkVERU5USUFMX0tJTkRfQkVBUkVSEAESHQoZQVBJX0NSRURFTlRJQUxfS0lORF9CQVNJQxACEh4KGkFQSV9DUkVERU5USUFMX0tJTkRfSEVBREVSEAMSHQoZQVBJX0NSRURFTlRJQUxfS0lORF9RVUVSWRAEEh4KGkFQSV9DUkVERU5USUFMX0tJTkRfT0FVVEgyEAUqpgIKHERhdGFzb3VyY2VDb25uZWN0b3JJbnRlcmZhY2USLgoqREFUQVNPVVJDRV9DT05ORUNUT1JfSU5URVJGQUNFX1VOU1BFQ0lGSUVEEAASKAokREFUQVNPVVJDRV9DT05ORUNUT1JfSU5URVJGQUNFX0ZJTEVTEAESKAokREFUQVNPVVJDRV9DT05ORUNUT1JfSU5URVJGQUNFX1BBR0VTEAISKgomREFUQVNPVVJDRV9DT05ORUNUT1JfSU5URVJGQUNFX1JFQ09SRFMQAxIrCidEQVRBU09VUkNFX0NPTk5FQ1RPUl9JTlRFUkZBQ0VfTUVTU0FHRVMQBBIpCiVEQVRBU09VUkNFX0NPTk5FQ1RPUl9JTlRFUkZBQ0VfRVZFTlRTEAUq7AEKGERhdGFzb3VyY2VDcmVkZW50aWFsTW9kZRIqCiZEQVRBU09VUkNFX0NSRURFTlRJQUxfTU9ERV9VTlNQRUNJRklFRBAAEiMKH0RBVEFTT1VSQ0VfQ1JFREVOVElBTF9NT0RFX05PTkUQARImCiJEQVRBU09VUkNFX0NSRURFTlRJQUxfTU9ERV9PUkdfQVBQEAISKQolREFUQVNPVVJDRV9DUkVERU5USUFMX01PREVfVVNFUl9PQVVUSBADEiwKKERBVEFTT1VSQ0VfQ1JFREVOVElBTF9NT0RFX1NUQVRJQ19TRUNSRVQQBCqXAQoWRGF0YXNvdXJjZVJlYWRlcnNNb2RlbBIoCiREQVRBU09VUkNFX1JFQURFUlNfTU9ERUxfVU5TUEVDSUZJRUQQABIqCiZEQVRBU09VUkNFX1JFQURFUlNfTU9ERUxfU09VUkNFX1NDT1BFRBABEicKI0RBVEFTT1VSQ0VfUkVBREVSU19NT0RFTF9UUkFOU0xBVEVEEAIq7gEKD1NvdXJjZVN5bmNQaGFzZRIhCh1TT1VSQ0VfU1lOQ19QSEFTRV9VTlNQRUNJRklFRBAAEhwKGFNPVVJDRV9TWU5DX1BIQVNFX1FVRVVFRBABEh4KGlNPVVJDRV9TWU5DX1BIQVNFX0ZFVENISU5HEAISHgoaU09VUkNFX1NZTkNfUEhBU0VfQ09NUElMRUQQAxIgChxTT1VSQ0VfU1lOQ19QSEFTRV9IQU5ERURfT0ZGEAQSGgoWU09VUkNFX1NZTkNfUEhBU0VfRE9ORRAFEhwKGFNPVVJDRV9TWU5DX1BIQVNFX0ZBSUxFRBAGKqYDChdTb3VyY2VTeW5jRmFpbHVyZVJlYXNvbhIqCiZTT1VSQ0VfU1lOQ19GQUlMVVJFX1JFQVNPTl9VTlNQRUNJRklFRBAAEisKJ1NPVVJDRV9TWU5DX0ZBSUxVUkVfUkVBU09OX1JBVEVfTElNSVRFRBABEikKJVNPVVJDRV9TWU5DX0ZBSUxVUkVfUkVBU09OX0NSRURFTlRJQUwQAhIsCihTT1VSQ0VfU1lOQ19GQUlMVVJFX1JFQVNPTl9BQ0NFU1NfREVOSUVEEAMSKAokU09VUkNFX1NZTkNfRkFJTFVSRV9SRUFTT05fTk9UX0ZPVU5EEAQSKAokU09VUkNFX1NZTkNfRkFJTFVSRV9SRUFTT05fVE9PX0xBUkdFEAUSLworU09VUkNFX1NZTkNfRkFJTFVSRV9SRUFTT05fSE9TVF9VTkFWQUlMQUJMRRAGEiQKIFNPVVJDRV9TWU5DX0ZBSUxVUkVfUkVBU09OX09USEVSEAcSLgoqU09VUkNFX1NZTkNfRkFJTFVSRV9SRUFTT05fREVMSVZFUllfRkFJTEVEEAgqwgEKEVNvdXJjZVN5bmNUcmlnZ2VyEiMKH1NPVVJDRV9TWU5DX1RSSUdHRVJfVU5TUEVDSUZJRUQQABIeChpTT1VSQ0VfU1lOQ19UUklHR0VSX01BTlVBTBABEiEKHVNPVVJDRV9TWU5DX1RSSUdHRVJfU0NIRURVTEVEEAISHwobU09VUkNFX1NZTkNfVFJJR0dFUl9XRUJIT09LEAMSJAogU09VUkNFX1NZTkNfVFJJR0dFUl9SRUNPTkZJR1VSRUQQBCqPAQoWRGF0YXNvdXJjZURvbWFpblN0YXR1cxIoCiREQVRBU09VUkNFX0RPTUFJTl9TVEFUVVNfVU5TUEVDSUZJRUQQABIkCiBEQVRBU09VUkNFX0RPTUFJTl9TVEFUVVNfUEVORElORxABEiUKIURBVEFTT1VSQ0VfRE9NQUlOX1NUQVRVU19WRVJJRklFRBACKpUDChpTb3VyY2VEZWxlZ2F0aW9uUmV2b2NhdGlvbhIsCihTT1VSQ0VfREVMRUdBVElPTl9SRVZPQ0FUSU9OX1VOU1BFQ0lGSUVEEAASKAokU09VUkNFX0RFTEVHQVRJT05fUkVWT0NBVElPTl9SRVZPS0VEEAESKQolU09VUkNFX0RFTEVHQVRJT05fUkVWT0NBVElPTl9SRVBMQUNFRBACEi8KK1NPVVJDRV9ERUxFR0FUSU9OX1JFVk9DQVRJT05fU09VUkNFX0RFTEVURUQQAxIvCitTT1VSQ0VfREVMRUdBVElPTl9SRVZPQ0FUSU9OX01FTUJFUl9SRU1PVkVEEAQSMAosU09VUkNFX0RFTEVHQVRJT05fUkVWT0NBVElPTl9QRVJNSVNTSU9OX0xPU1QQBRIuCipTT1VSQ0VfREVMRUdBVElPTl9SRVZPQ0FUSU9OX1VTRVJfSU5BQ1RJVkUQBhIwCixTT1VSQ0VfREVMRUdBVElPTl9SRVZPQ0FUSU9OX0JJTkRJTkdfQ0hBTkdFRBAHMrE5ChFEYXRhc291cmNlU2VydmljZRK5AwocUHJ1bmVTb3VyY2VPcGVyYXRpb25SZWNlaXB0cxI1LnNhYXMuYWNjb3VudHMudjEuUHJ1bmVTb3VyY2VPcGVyYXRpb25SZWNlaXB0c1JlcXVlc3QaNi5zYWFzLmFjY291bnRzLnYxLlBydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXNwb25zZSKpAsLzGGwIAhADGhFkYXRhc291cmNlOmludm9rZSIRZGF0YXNvdXJjZTppbnZva2UqDwoJc291cmNlX2lkEAQYAzABOiMKH3NhYXMuZGF0YXNvdXJjZS5yZWNlaXB0cy5wcnVuZWQQAkADSARQA1gDYAHa8xi0AQoCCDwSAwjYBBgFIgIIAioLVU5BVkFJTEFCTEUqElJFU09VUkNFX0VYSEFVU1RFRCoHQUJPUlRFRDIPc2Fhcy1kYXRhc291cmNlSkYvc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlU2VydmljZS9Mb29rdXBQcnVuZVNvdXJjZU9wZXJhdGlvblJlY2VpcHRzUIAgWIAgYAFyGAoGc291cmNlEgZpbnZva2USBHJlYWQYARLmAQoiTG9va3VwUHJ1bmVTb3VyY2VPcGVyYXRpb25SZWNlaXB0cxI7LnNhYXMuYWNjb3VudHMudjEuTG9va3VwUHJ1bmVTb3VyY2VPcGVyYXRpb25SZWNlaXB0c1JlcXVlc3QaNi5zYWFzLmFjY291bnRzLnYxLlBydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXNwb25zZSJLwvMYRwgCEAMaD2RhdGFzb3VyY2U6cmVhZCIPZGF0YXNvdXJjZTpyZWFkKg8KCWVmZmVjdF9pZBAEGAMwAToCEAFAAUgDUANYA2ABEvwDChVJbnZva2VTb3VyY2VPcGVyYXRpb24SLi5zYWFzLmFjY291bnRzLnYxLkludm9rZVNvdXJjZU9wZXJhdGlvblJlcXVlc3QaLy5zYWFzLmFjY291bnRzLnYxLkludm9rZVNvdXJjZU9wZXJhdGlvblJlc3BvbnNlIoEDwvMYbggCEAMaEWRhdGFzb3VyY2U6aW52b2tlIhFkYXRhc291cmNlOmludm9rZSoPCglzb3VyY2VfaWQQBBgDMAE6JQohc2Fhcy5kYXRhc291cmNlLm9wZXJhdGlvbi5pbnZva2VkEARAA0gEUARYBGAB2vMYrwEKAgg8EgMI2AQYBSICCAIqC1VOQVZBSUxBQkxFKhJSRVNPVVJDRV9FWEhBVVNURUQqB0FCT1JURUQyD3NhYXMtZGF0YXNvdXJjZUo/L3NhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVNlcnZpY2UvTG9va3VwSW52b2tlU291cmNlT3BlcmF0aW9uUICABFiAgEBgAXIYCgZzb3VyY2USBmludm9rZRIEcmVhZBgBgtPkkwJVOgEqIlAvdjEvb3JnYW5pemF0aW9ucy97b3JnX2lkfS9kYXRhc291cmNlcy97c291cmNlX2lkfS9vcGVyYXRpb25zL3tvcGVyYXRpb259Omludm9rZRKHAgobTG9va3VwSW52b2tlU291cmNlT3BlcmF0aW9uEjQuc2Fhcy5hY2NvdW50cy52MS5Mb29rdXBJbnZva2VTb3VyY2VPcGVyYXRpb25SZXF1ZXN0Gi8uc2Fhcy5hY2NvdW50cy52MS5JbnZva2VTb3VyY2VPcGVyYXRpb25SZXNwb25zZSKAAcLzGEcIAhADGg9kYXRhc291cmNlOnJlYWQiD2RhdGFzb3VyY2U6cmVhZCoPCgllZmZlY3RfaWQQBBgDMAE6AhABQAFIA1ADWARgAYLT5JMCLxItL3YxL2RhdGFzb3VyY2Utb3BlcmF0aW9uLXJlY2VpcHRzL3tlZmZlY3RfaWR9EoUCChRMaXN0U291cmNlT3BlcmF0aW9ucxItLnNhYXMuYWNjb3VudHMudjEuTGlzdFNvdXJjZU9wZXJhdGlvbnNSZXF1ZXN0Gi4uc2Fhcy5hY2NvdW50cy52MS5MaXN0U291cmNlT3BlcmF0aW9uc1Jlc3BvbnNlIo0BwvMYRAgCEAMaD2RhdGFzb3VyY2U6cmVhZCIPZGF0YXNvdXJjZTpyZWFkKgwKBm9yZ19pZBACGAEwAToCEAFAAUgDUANYA2ABgtPkkwI/Ej0vdjEvb3JnYW5pemF0aW9ucy97b3JnX2lkfS9kYXRhc291cmNlcy97c291cmNlX2lkfS9vcGVyYXRpb25zEpMCChdEZWNsYXJlU291cmNlT3BlcmF0aW9ucxIwLnNhYXMuYWNjb3VudHMudjEuRGVjbGFyZVNvdXJjZU9wZXJhdGlvbnNSZXF1ZXN0GjEuc2Fhcy5hY2NvdW50cy52MS5EZWNsYXJlU291cmNlT3BlcmF0aW9uc1Jlc3BvbnNlIpIBwvMYRggCEAQqDAoGb3JnX2lkEAIYATABOiYKInNhYXMuZGF0YXNvdXJjZS5vcGVyYXRpb24uZGVjbGFyZWQQAkABSARQA1gDYAGC0+STAkI6ASoiPS92MS9vcmdhbml6YXRpb25zL3tvcmdfaWR9L2RhdGFzb3VyY2VzL3tzb3VyY2VfaWR9L29wZXJhdGlvbnMSrAEKD0FkZEdpdEh1YlNvdXJjZRIoLnNhYXMuYWNjb3VudHMudjEuQWRkR2l0SHViU291cmNlUmVxdWVzdBopLnNhYXMuYWNjb3VudHMudjEuQWRkR2l0SHViU291cmNlUmVzcG9uc2UiRMLzGEAIAhAEKgwKBm9yZ19pZBACGAEwATogChxzYWFzLmRhdGFzb3VyY2Uuc291cmNlLmFkZGVkEAJAAUgEUARYA2ABEpoBCglBZGRTb3VyY2USIi5zYWFzLmFjY291bnRzLnYxLkFkZFNvdXJjZVJlcXVlc3QaIy5zYWFzLmFjY291bnRzLnYxLkFkZFNvdXJjZVJlc3BvbnNlIkTC8xhACAIQBCoMCgZvcmdfaWQQAhgBMAE6IAocc2Fhcy5kYXRhc291cmNlLnNvdXJjZS5hZGRlZBACQAFIBFAEWANgARKlAQoMVXBkYXRlU291cmNlEiUuc2Fhcy5hY2NvdW50cy52MS5VcGRhdGVTb3VyY2VSZXF1ZXN0GiYuc2Fhcy5hY2NvdW50cy52MS5VcGRhdGVTb3VyY2VSZXNwb25zZSJGwvMYQggCEAQqDAoGb3JnX2lkEAIYATABOiIKHnNhYXMuZGF0YXNvdXJjZS5zb3VyY2UudXBkYXRlZBACQAFIBFACWANgARKPAQoUR2V0RGF0YXNvdXJjZUNhdGFsb2cSLS5zYWFzLmFjY291bnRzLnYxLkdldERhdGFzb3VyY2VDYXRhbG9nUmVxdWVzdBouLnNhYXMuYWNjb3VudHMudjEuR2V0RGF0YXNvdXJjZUNhdGFsb2dSZXNwb25zZSIYwvMYFAgCEAEwAToCEAFAAUgDUAJYAmABEoIBCgtMaXN0U291cmNlcxIkLnNhYXMuYWNjb3VudHMudjEuTGlzdFNvdXJjZXNSZXF1ZXN0GiUuc2Fhcy5hY2NvdW50cy52MS5MaXN0U291cmNlc1Jlc3BvbnNlIibC8xgiCAIQAyoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARJ8CglHZXRTb3VyY2USIi5zYWFzLmFjY291bnRzLnYxLkdldFNvdXJjZVJlcXVlc3QaIy5zYWFzLmFjY291bnRzLnYxLkdldFNvdXJjZVJlc3BvbnNlIibC8xgiCAIQAyoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARKeAQoKU3luY1NvdXJjZRIjLnNhYXMuYWNjb3VudHMudjEuU3luY1NvdXJjZVJlcXVlc3QaJC5zYWFzLmFjY291bnRzLnYxLlN5bmNTb3VyY2VSZXNwb25zZSJFwvMYQQgCEAQqDAoGb3JnX2lkEAIYATABOiEKHXNhYXMuZGF0YXNvdXJjZS5zb3VyY2Uuc3luY2VkEAJAAUgEUARYAmABEogBCg1HZXRTb3VyY2VTeW5jEiYuc2Fhcy5hY2NvdW50cy52MS5HZXRTb3VyY2VTeW5jUmVxdWVzdBonLnNhYXMuYWNjb3VudHMudjEuR2V0U291cmNlU3luY1Jlc3BvbnNlIibC8xgiCAIQBCoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARKlAQoMRGVsZXRlU291cmNlEiUuc2Fhcy5hY2NvdW50cy52MS5EZWxldGVTb3VyY2VSZXF1ZXN0GiYuc2Fhcy5hY2NvdW50cy52MS5EZWxldGVTb3VyY2VSZXNwb25zZSJGwvMYQggCEAQqDAoGb3JnX2lkEAIYATABOiIKHnNhYXMuZGF0YXNvdXJjZS5zb3VyY2UucmVtb3ZlZBACQAFIBFACWAJgARLEAQoTQmVnaW5HaXRIdWJBcHBTZXR1cBIsLnNhYXMuYWNjb3VudHMudjEuQmVnaW5HaXRIdWJBcHBTZXR1cFJlcXVlc3QaLS5zYWFzLmFjY291bnRzLnYxLkJlZ2luR2l0SHViQXBwU2V0dXBSZXNwb25zZSJQwvMYTAgCEAQqDAoGb3JnX2lkEAIYATABOiwKKHNhYXMuZGF0YXNvdXJjZS5naXRodWJfYXBwLnNldHVwX3N0YXJ0ZWQQAkABSARQAlgEYAESzwEKFkNvbXBsZXRlR2l0SHViQXBwU2V0dXASLy5zYWFzLmFjY291bnRzLnYxLkNvbXBsZXRlR2l0SHViQXBwU2V0dXBSZXF1ZXN0GjAuc2Fhcy5hY2NvdW50cy52MS5Db21wbGV0ZUdpdEh1YkFwcFNldHVwUmVzcG9uc2UiUsLzGE4IAhAEKgwKBm9yZ19pZBACGAEwATouCipzYWFzLmRhdGFzb3VyY2UuZ2l0aHViX2FwcC5zZXR1cF9jb21wbGV0ZWQQAkABSARQBFgDYAESzQEKGE1pZ3JhdGVHaXRIdWJTb3VyY2VUb0FwcBIxLnNhYXMuYWNjb3VudHMudjEuTWlncmF0ZUdpdEh1YlNvdXJjZVRvQXBwUmVxdWVzdBoyLnNhYXMuYWNjb3VudHMudjEuTWlncmF0ZUdpdEh1YlNvdXJjZVRvQXBwUmVzcG9uc2UiSsLzGEYIAhAEKgwKBm9yZ19pZBACGAEwATomCiJzYWFzLmRhdGFzb3VyY2UuY3JlZGVudGlhbC51cGRhdGVkEAJAAUgEUAJYA2ABEtUBChpCZWdpbkRhdGFzb3VyY2VBY2NvdW50TGluaxIzLnNhYXMuYWNjb3VudHMudjEuQmVnaW5EYXRhc291cmNlQWNjb3VudExpbmtSZXF1ZXN0GjQuc2Fhcy5hY2NvdW50cy52MS5CZWdpbkRhdGFzb3VyY2VBY2NvdW50TGlua1Jlc3BvbnNlIkzC8xhICAIQAyoMCgZvcmdfaWQQAhgBMAE6KAokc2Fhcy5kYXRhc291cmNlLmFjY291bnRfbGlua19zdGFydGVkEAJAAUgEUAJYBGABEtgBCh1Db21wbGV0ZURhdGFzb3VyY2VBY2NvdW50TGluaxI2LnNhYXMuYWNjb3VudHMudjEuQ29tcGxldGVEYXRhc291cmNlQWNjb3VudExpbmtSZXF1ZXN0Gjcuc2Fhcy5hY2NvdW50cy52MS5Db21wbGV0ZURhdGFzb3VyY2VBY2NvdW50TGlua1Jlc3BvbnNlIkbC8xhCCAIQAyoMCgZvcmdfaWQQAhgBMAE6Igoec2Fhcy5kYXRhc291cmNlLmFjY291bnRfbGlua2VkEAJAAUgEUARYA2ABErUBChxMaXN0TXlEYXRhc291cmNlQWNjb3VudExpbmtzEjUuc2Fhcy5hY2NvdW50cy52MS5MaXN0TXlEYXRhc291cmNlQWNjb3VudExpbmtzUmVxdWVzdBo2LnNhYXMuYWNjb3VudHMudjEuTGlzdE15RGF0YXNvdXJjZUFjY291bnRMaW5rc1Jlc3BvbnNlIibC8xgiCAIQAyoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARLUAQobRGVsZXRlRGF0YXNvdXJjZUFjY291bnRMaW5rEjQuc2Fhcy5hY2NvdW50cy52MS5EZWxldGVEYXRhc291cmNlQWNjb3VudExpbmtSZXF1ZXN0GjUuc2Fhcy5hY2NvdW50cy52MS5EZWxldGVEYXRhc291cmNlQWNjb3VudExpbmtSZXNwb25zZSJIwvMYRAgCEAMqDAoGb3JnX2lkEAIYATABOiQKIHNhYXMuZGF0YXNvdXJjZS5hY2NvdW50X3VubGlua2VkEAJAAUgEUAJYA2ABEqMBChZHZXREYXRhc291cmNlRGlyZWN0b3J5Ei8uc2Fhcy5hY2NvdW50cy52MS5HZXREYXRhc291cmNlRGlyZWN0b3J5UmVxdWVzdBowLnNhYXMuYWNjb3VudHMudjEuR2V0RGF0YXNvdXJjZURpcmVjdG9yeVJlc3BvbnNlIibC8xgiCAIQBCoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARK3AQoTQmluZERhdGFzb3VyY2VHcm91cBIsLnNhYXMuYWNjb3VudHMudjEuQmluZERhdGFzb3VyY2VHcm91cFJlcXVlc3QaLS5zYWFzLmFjY291bnRzLnYxLkJpbmREYXRhc291cmNlR3JvdXBSZXNwb25zZSJDwvMYPwgCEAQqDAoGb3JnX2lkEAIYATABOh8KG3NhYXMuZGF0YXNvdXJjZS5ncm91cF9ib3VuZBACQAFIBFACWANgARK/AQoVVW5iaW5kRGF0YXNvdXJjZUdyb3VwEi4uc2Fhcy5hY2NvdW50cy52MS5VbmJpbmREYXRhc291cmNlR3JvdXBSZXF1ZXN0Gi8uc2Fhcy5hY2NvdW50cy52MS5VbmJpbmREYXRhc291cmNlR3JvdXBSZXNwb25zZSJFwvMYQQgCEAQqDAoGb3JnX2lkEAIYATABOiEKHXNhYXMuZGF0YXNvdXJjZS5ncm91cF91bmJvdW5kEAJAAUgEUAJYA2ABEsABChVDbGFpbURhdGFzb3VyY2VEb21haW4SLi5zYWFzLmFjY291bnRzLnYxLkNsYWltRGF0YXNvdXJjZURvbWFpblJlcXVlc3QaLy5zYWFzLmFjY291bnRzLnYxLkNsYWltRGF0YXNvdXJjZURvbWFpblJlc3BvbnNlIkbC8xhCCAIQBCoMCgZvcmdfaWQQAhgBMAE6Igoec2Fhcy5kYXRhc291cmNlLmRvbWFpbl9jbGFpbWVkEAJAAUgEUAJYA2ABEsQBChZWZXJpZnlEYXRhc291cmNlRG9tYWluEi8uc2Fhcy5hY2NvdW50cy52MS5WZXJpZnlEYXRhc291cmNlRG9tYWluUmVxdWVzdBowLnNhYXMuYWNjb3VudHMudjEuVmVyaWZ5RGF0YXNvdXJjZURvbWFpblJlc3BvbnNlIkfC8xhDCAIQBCoMCgZvcmdfaWQQAhgBMAE6Iwofc2Fhcy5kYXRhc291cmNlLmRvbWFpbl92ZXJpZmllZBACQAFIBFACWANgARLDAQoWRGVsZXRlRGF0YXNvdXJjZURvbWFpbhIvLnNhYXMuYWNjb3VudHMudjEuRGVsZXRlRGF0YXNvdXJjZURvbWFpblJlcXVlc3QaMC5zYWFzLmFjY291bnRzLnYxLkRlbGV0ZURhdGFzb3VyY2VEb21haW5SZXNwb25zZSJGwvMYQggCEAQqDAoGb3JnX2lkEAIYATABOiIKHnNhYXMuZGF0YXNvdXJjZS5kb21haW5fcmVtb3ZlZBACQAFIBFACWANgARKgAQoVTGlzdFNvdXJjZURlbGVnYXRpb25zEi4uc2Fhcy5hY2NvdW50cy52MS5MaXN0U291cmNlRGVsZWdhdGlvbnNSZXF1ZXN0Gi8uc2Fhcy5hY2NvdW50cy52MS5MaXN0U291cmNlRGVsZWdhdGlvbnNSZXNwb25zZSImwvMYIggCEAQqDAoGb3JnX2lkEAIYATABOgIQAUABSANQAlgDYAESxwEKFlJldm9rZVNvdXJjZURlbGVnYXRpb24SLy5zYWFzLmFjY291bnRzLnYxLlJldm9rZVNvdXJjZURlbGVnYXRpb25SZXF1ZXN0GjAuc2Fhcy5hY2NvdW50cy52MS5SZXZva2VTb3VyY2VEZWxlZ2F0aW9uUmVzcG9uc2UiSsLzGEYIAhAEKgwKBm9yZ19pZBACGAEwATomCiJzYWFzLmRhdGFzb3VyY2UuZGVsZWdhdGlvbi5yZXZva2VkEAJAAUgEUAJYA2ABEusBChZEZWxlZ2F0ZVNvdXJjZVRyaWdnZXJzEi8uc2Fhcy5hY2NvdW50cy52MS5EZWxlZ2F0ZVNvdXJjZVRyaWdnZXJzUmVxdWVzdBowLnNhYXMuYWNjb3VudHMudjEuRGVsZWdhdGVTb3VyY2VUcmlnZ2Vyc1Jlc3BvbnNlIm7C8xhqCAIQBCoMCgZvcmdfaWQQAhgBMAE6Sgoic2Fhcy5kYXRhc291cmNlLmRlbGVnYXRpb24uY3JlYXRlZAoic2Fhcy5kYXRhc291cmNlLmRlbGVnYXRpb24ucmV2b2tlZBACQAFIBFACWANgARKyAQobR2V0U291cmNlVHJpZ2dlckRlY2xhcmF0aW9uEjQuc2Fhcy5hY2NvdW50cy52MS5HZXRTb3VyY2VUcmlnZ2VyRGVjbGFyYXRpb25SZXF1ZXN0GjUuc2Fhcy5hY2NvdW50cy52MS5HZXRTb3VyY2VUcmlnZ2VyRGVjbGFyYXRpb25SZXNwb25zZSImwvMYIggCEAQqDAoGb3JnX2lkEAIYATABOgIQAUABSANQAlgDYAESgwMKD1JlY29uY2lsZVNvdXJjZRIoLnNhYXMuYWNjb3VudHMudjEuUmVjb25jaWxlU291cmNlUmVxdWVzdBopLnNhYXMuYWNjb3VudHMudjEuUmVjb25jaWxlU291cmNlUmVzcG9uc2UimgLC8xhqCAIQAxoRZGF0YXNvdXJjZTppbnZva2UiEWRhdGFzb3VyY2U6aW52b2tlKg8KCXNvdXJjZV9pZBAEGAMwATohCh1zYWFzLmRhdGFzb3VyY2Uuc291cmNlLnN5bmNlZBACQANIBFACWAJgAdrzGKcBCgIIPBIDCNgEGAUiAggCKgtVTkFWQUlMQUJMRSoSUkVTT1VSQ0VfRVhIQVVTVEVEKgdBQk9SVEVEMg9zYWFzLWRhdGFzb3VyY2VKOS9zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VTZXJ2aWNlL0xvb2t1cFJlY29uY2lsZVNvdXJjZVCAIFiAIGABchgKBnNvdXJjZRIGaW52b2tlEgRyZWFkGAESvwEKFUxvb2t1cFJlY29uY2lsZVNvdXJjZRIuLnNhYXMuYWNjb3VudHMudjEuTG9va3VwUmVjb25jaWxlU291cmNlUmVxdWVzdBopLnNhYXMuYWNjb3VudHMudjEuUmVjb25jaWxlU291cmNlUmVzcG9uc2UiS8LzGEcIAhADGg9kYXRhc291cmNlOnJlYWQiD2RhdGFzb3VyY2U6cmVhZCoPCgllZmZlY3RfaWQQBBgDMAE6AhABQAFIA1ACWAJgAUK3AQoUY29tLnNhYXMuYWNjb3VudHMudjFCD0RhdGFzb3VyY2VQcm90b1ABWixhY2NvdW50cy9wa2cvZ2VuL3NhYXMvYWNjb3VudHMvdjE7YWNjb3VudHN2MaICA1NBWKoCEFNhYXMuQWNjb3VudHMuVjHKAhBTYWFzXEFjY291bnRzXFYx4gIcU2Fhc1xBY2NvdW50c1xWMVxHUEJNZXRhZGF0YeoCElNhYXM6OkFjY291bnRzOjpWMWIGcHJvdG8z", [file_buf_validate_validate, file_codefly_runnable_v0_options, file_google_api_annotations, file_google_protobuf_duration, file_google_protobuf_struct, file_google_protobuf_timestamp, file_saas_jobs_v1_jobs, file_saas_policy_v1_options]); + fileDesc("CiFzYWFzL2FjY291bnRzL3YxL2RhdGFzb3VyY2UucHJvdG8SEHNhYXMuYWNjb3VudHMudjEiXgoWR2l0SHViRGF0YXNvdXJjZUNvbmZpZxIMCgRyZXBvGAEgASgJEg0KBXBhdGhzGAIgAygJEg4KBmJyYW5jaBgDIAEoCRIXCg9maWxlX2V4dGVuc2lvbnMYBCADKAki9AEKD0FwaU9BdXRoMkNvbmZpZxI2CgVncmFudBgEIAEoDjInLnNhYXMuYWNjb3VudHMudjEuQXBpT0F1dGgyQ29uZmlnLkdyYW50EhEKCXRva2VuX3VybBgBIAEoCRIRCgljbGllbnRfaWQYAiABKAkSDgoGc2NvcGVzGAMgAygJInMKBUdyYW50EhUKEUdSQU5UX1VOU1BFQ0lGSUVEEAASFwoTR1JBTlRfUkVGUkVTSF9UT0tFThABEhwKGEdSQU5UX0NMSUVOVF9DUkVERU5USUFMUxACEhwKGEdSQU5UX0FVVEhPUklaQVRJT05fQ09ERRADIuoBChNBcGlEYXRhc291cmNlQ29uZmlnEhAKCGJhc2VfdXJsGAEgASgJEhUKDXJlc291cmNlX3BhdGgYAiABKAkSPAoPY3JlZGVudGlhbF9raW5kGAMgASgOMiMuc2Fhcy5hY2NvdW50cy52MS5BcGlDcmVkZW50aWFsS2luZBIZChFjcmVkZW50aWFsX2hlYWRlchgEIAEoCRIeChZjcmVkZW50aWFsX3F1ZXJ5X3BhcmFtGAUgASgJEjEKBm9hdXRoMhgGIAEoCzIhLnNhYXMuYWNjb3VudHMudjEuQXBpT0F1dGgyQ29uZmlnIkEKF0NyYXdsZXJEYXRhc291cmNlQ29uZmlnEhMKC3NpdGVtYXBfdXJsGAEgASgJEhEKCW1heF9wYWdlcxgCIAEoDSKrAgoWVXBsb2FkRGF0YXNvdXJjZUNvbmZpZxIQCghlbmRwb2ludBgBIAEoCRIOCgZyZWdpb24YAiABKAkSDgoGYnVja2V0GAMgASgJEg4KBnByZWZpeBgEIAEoCRIVCg1hY2Nlc3Nfa2V5X2lkGAUgASgJEhMKC21heF9vYmplY3RzGAYgASgNEisKEGluY2x1ZGVfcGF0dGVybnMYByADKAlCEbpIDpIBCxAQIgdyBRABGIACEisKEGV4Y2x1ZGVfcGF0dGVybnMYCCADKAlCEbpIDpIBCxAQIgdyBRABGIACEkkKD2ZpbGVfZXh0ZW5zaW9ucxgJIAMoCUIwukgtkgEqECAiJnIkEAIYITIeXlwuW0EtWmEtejAtOV1bQS1aYS16MC05Ll8tXSokIoMHCgpEYXRhc291cmNlEgoKAmlkGAEgASgJEg4KBm9yZ19pZBgCIAEoCRI2Cghwcm92aWRlchgDIAEoDjIkLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVByb3ZpZGVyEjgKBmdpdGh1YhgFIAEoCzIoLnNhYXMuYWNjb3VudHMudjEuR2l0SHViRGF0YXNvdXJjZUNvbmZpZxIyCgZzdGF0dXMYBiABKA4yIi5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VTdGF0dXMSGgoSd2ViaG9va19jb25maWd1cmVkGAcgASgIEi4KCmNyZWF0ZWRfYXQYCCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnVwZGF0ZWRfYXQYCSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjIKDmxhc3Rfc3luY2VkX2F0GAogASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIyCgNhcGkYCyABKAsyJS5zYWFzLmFjY291bnRzLnYxLkFwaURhdGFzb3VyY2VDb25maWcSOgoHY3Jhd2xlchgMIAEoCzIpLnNhYXMuYWNjb3VudHMudjEuQ3Jhd2xlckRhdGFzb3VyY2VDb25maWcSOAoGdXBsb2FkGA0gASgLMiguc2Fhcy5hY2NvdW50cy52MS5VcGxvYWREYXRhc291cmNlQ29uZmlnEhgKEGJvdW5kYXJ5X25vZGVfaWQYDiABKAkSNAoQbGFzdF9pbmdlc3RlZF9hdBgPIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASHAoUbGFzdF9pbmdlc3RlZF9jb21taXQYECABKAkSFQoNc3RhdHVzX3JlYXNvbhgRIAEoCRIWCg5ib3VuZGFyeV9sYWJlbBgSIAEoCRISCgpjb25mb3JtYW50GBMgASgIEhcKD2NvbmZvcm1hbmNlX2dhcBgUIAEoCRI/Cg1saXZlX2RlbGl2ZXJ5GBUgASgOMiguc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlTGl2ZURlbGl2ZXJ5EjUKEnJlY29uY2lsZV9pbnRlcnZhbBgWIAEoCzIZLmdvb2dsZS5wcm90b2J1Zi5EdXJhdGlvbkoECAQQBVIRdGFyZ2V0X2NvbGxlY3Rpb24isgMKFkFkZEdpdEh1YlNvdXJjZVJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARI7CgRyZXBvGAIgASgJQi26SCpyKBADGP8BMiFeW0EtWmEtejAtOV8uLV0rL1tBLVphLXowLTlfLi1dKyQSIAoFcGF0aHMYAyADKAlCEbpIDpIBCxBAIgdyBRABGIAEEhgKBmJyYW5jaBgEIAEoCUIIukgFcgMY/wESJAoQYm91bmRhcnlfbm9kZV9pZBgIIAEoCUIIukgFcgOwAQFIABImChBjb2xsZWN0aW9uX2xhYmVsGAkgASgJQgq6SAdyBRABGP8BSAASHgoMYWNjZXNzX3Rva2VuGAYgASgJQgi6SAVyAxiACBIgCg53ZWJob29rX3NlY3JldBgHIAEoCUIIukgFcgMYgAgSSQoPZmlsZV9leHRlbnNpb25zGAogAygJQjC6SC2SASoQICImciQQAhghMh5eXC5bQS1aYS16MC05XVtBLVphLXowLTkuXy1dKiRCEQoIYm91bmRhcnkSBbpIAggBSgQIBRAGUhF0YXJnZXRfY29sbGVjdGlvbiJLChdBZGRHaXRIdWJTb3VyY2VSZXNwb25zZRIwCgpkYXRhc291cmNlGAEgASgLMhwuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlIsgEChBBZGRTb3VyY2VSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESQgoIcHJvdmlkZXIYAiABKA4yJC5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VQcm92aWRlckIKukgHggEEEAEgABI6CgZnaXRodWIYBCABKAsyKC5zYWFzLmFjY291bnRzLnYxLkdpdEh1YkRhdGFzb3VyY2VDb25maWdIABI0CgNhcGkYBSABKAsyJS5zYWFzLmFjY291bnRzLnYxLkFwaURhdGFzb3VyY2VDb25maWdIABI8CgdjcmF3bGVyGAggASgLMikuc2Fhcy5hY2NvdW50cy52MS5DcmF3bGVyRGF0YXNvdXJjZUNvbmZpZ0gAEjoKBnVwbG9hZBgJIAEoCzIoLnNhYXMuYWNjb3VudHMudjEuVXBsb2FkRGF0YXNvdXJjZUNvbmZpZ0gAEiQKEGJvdW5kYXJ5X25vZGVfaWQYDCABKAlCCLpIBXIDsAEBSAESJgoQY29sbGVjdGlvbl9sYWJlbBgNIAEoCUIKukgHcgUQARj/AUgBEhwKCmNyZWRlbnRpYWwYBiABKAlCCLpIBXIDGIAgEiAKDndlYmhvb2tfc2VjcmV0GAcgASgJQgi6SAVyAxiACBImChRvYXV0aDJfY2xpZW50X3NlY3JldBgKIAEoCUIIukgFcgMYgAhCCAoGY29uZmlnQhEKCGJvdW5kYXJ5EgW6SAIIAUoECAMQBFIRdGFyZ2V0X2NvbGxlY3Rpb24iRQoRQWRkU291cmNlUmVzcG9uc2USMAoKZGF0YXNvdXJjZRgBIAEoCzIcLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZSLkAgoTVXBkYXRlU291cmNlUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhQKAmlkGAIgASgJQgi6SAVyA7ABARIYCgZicmFuY2gYAyABKAlCCLpIBXIDGP8BEiAKBXBhdGhzGAQgAygJQhG6SA6SAQsQQCIHcgUQARiABBJJCg9maWxlX2V4dGVuc2lvbnMYBSADKAlCMLpILZIBKhAgIiZyJBACGCEyHl5cLltBLVphLXowLTldW0EtWmEtejAtOS5fLV0qJBI8CghleHBlY3RlZBgGIAEoCzIiLnNhYXMuYWNjb3VudHMudjEuU291cmNlRmV0Y2hTY29wZUIGukgDyAEBEisKEGluY2x1ZGVfcGF0dGVybnMYByADKAlCEbpIDpIBCxAQIgdyBRABGIACEisKEGV4Y2x1ZGVfcGF0dGVybnMYCCADKAlCEbpIDpIBCxAQIgdyBRABGIACIs8BChBTb3VyY2VGZXRjaFNjb3BlEhgKBmJyYW5jaBgBIAEoCUIIukgFcgMY/wESIAoFcGF0aHMYAiADKAlCEbpIDpIBCxBAIgdyBRABGIAEEikKD2ZpbGVfZXh0ZW5zaW9ucxgDIAMoCUIQukgNkgEKECAiBnIEEAEYIRIpChBpbmNsdWRlX3BhdHRlcm5zGAQgAygJQg+6SAySAQkQECIFcgMYgAISKQoQZXhjbHVkZV9wYXR0ZXJucxgFIAMoCUIPukgMkgEJEBAiBXIDGIACIlgKFFVwZGF0ZVNvdXJjZVJlc3BvbnNlEjAKCmRhdGFzb3VyY2UYASABKAsyHC5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2USDgoGam9iX2lkGAIgASgJIloKFURhdGFzb3VyY2VDb25maWdGaWVsZBILCgNrZXkYASABKAkSFAoMZGlzcGxheV9uYW1lGAIgASgJEgwKBGhlbHAYAyABKAkSEAoIcmVxdWlyZWQYBCABKAgixAEKGURhdGFzb3VyY2VDb25uZWN0b3JCdWRnZXQSGgoSbWF4X2l0ZW1zX3Blcl9jYWxsGAEgASgNEhoKEm1heF9ieXRlc19wZXJfY2FsbBgCIAEoAxIWCg5tYXhfaXRlbV9ieXRlcxgDIAEoAxIdChVvcGVyYXRpb25zX3Blcl93aW5kb3cYBCABKA0SFgoOd2luZG93X3NlY29uZHMYBSABKAMSIAoYYmFja2dyb3VuZF9zaGFyZV9wZXJjZW50GAYgASgNIqoGChxEYXRhc291cmNlUHJvdmlkZXJEZXNjcmlwdG9yEjYKCHByb3ZpZGVyGAEgASgOMiQuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlUHJvdmlkZXISFAoMZGlzcGxheV9uYW1lGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEj4KDWNvbmZpZ19maWVsZHMYBCADKAsyJy5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VDb25maWdGaWVsZBIYChBzdXBwb3J0c193ZWJob29rGAUgASgIEkcKGnN1cHBvcnRlZF9jcmVkZW50aWFsX2tpbmRzGAYgAygOMiMuc2Fhcy5hY2NvdW50cy52MS5BcGlDcmVkZW50aWFsS2luZBIRCgljb25uZWN0b3IYByABKAkSQQoJaW50ZXJmYWNlGAggASgOMi4uc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlQ29ubmVjdG9ySW50ZXJmYWNlEkQKEGNyZWRlbnRpYWxfbW9kZXMYCSADKA4yKi5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VDcmVkZW50aWFsTW9kZRI/Cg1yZWFkZXJzX21vZGVsGAogASgOMiguc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlUmVhZGVyc01vZGVsEjsKBmJ1ZGdldBgLIAEoCzIrLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZUNvbm5lY3RvckJ1ZGdldBISCgpjb25mb3JtYW50GAwgASgIEhcKD2NvbmZvcm1hbmNlX2dhcBgNIAEoCRIbChNhY2NlcHRzX25ld19zb3VyY2VzGA4gASgIEiAKGGxpdmVfZGVsaXZlcnlfY29uZmlndXJlZBgPIAEoCBIaChJhY2NlcHRzX29wZXJhdGlvbnMYECABKAgSFgoOb3BlcmF0aW9uc19nYXAYESABKAkSGwoTb2F1dGhfYXV0aG9yaXplX3VybBgSIAEoCRIXCg9vYXV0aF90b2tlbl91cmwYEyABKAkSFAoMb2F1dGhfc2NvcGVzGBQgAygJIh0KG0dldERhdGFzb3VyY2VDYXRhbG9nUmVxdWVzdCJhChxHZXREYXRhc291cmNlQ2F0YWxvZ1Jlc3BvbnNlEkEKCXByb3ZpZGVycxgBIAMoCzIuLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVByb3ZpZGVyRGVzY3JpcHRvciIuChJMaXN0U291cmNlc1JlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABASJIChNMaXN0U291cmNlc1Jlc3BvbnNlEjEKC2RhdGFzb3VyY2VzGAEgAygLMhwuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlIkIKEEdldFNvdXJjZVJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIUCgJpZBgCIAEoCUIIukgFcgOwAQEiRQoRR2V0U291cmNlUmVzcG9uc2USMAoKZGF0YXNvdXJjZRgBIAEoCzIcLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZSJ2ChFTeW5jU291cmNlUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhQKAmlkGAIgASgJQgi6SAVyA7ABARIeCgxhY2Nlc3NfdG9rZW4YAyABKAlCCLpIBXIDGIAgEhEKCXJlY29ubmVjdBgEIAEoCCIkChJTeW5jU291cmNlUmVzcG9uc2USDgoGam9iX2lkGAEgASgJImoKFEdldFNvdXJjZVN5bmNSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGwoJc291cmNlX2lkGAIgASgJQgi6SAVyA7ABARIbCgZqb2JfaWQYAyABKAlCC7pICNgBAXIDsAEBIpUBChJTb3VyY2VTeW5jRGVsaXZlcnkSGAoGam9iX2lkGAEgASgJQgi6SAVyA7ABARItCgVzdGF0ZRgCIAEoDjIWLnNhYXMuam9icy52MS5Kb2JTdGF0ZUIGukgDyAEBEjYKCWV4ZWN1dGlvbhgDIAEoCzIjLnNhYXMuam9icy52MS5Kb2JFeGVjdXRpb25SZWZlcmVuY2UiiwEKEVNvdXJjZVN5bmNDaGFuZ2VzEg0KBWZpbGVzGAEgASgNEg0KBWFkZGVkGAIgASgNEhAKCG1vZGlmaWVkGAMgASgNEg8KB2RlbGV0ZWQYBCABKA0SEwoLc3BsaXRfa25vd24YBSABKAgSEAoIc25hcHNob3QYBiABKAgSDgoGY29tbWl0GAcgASgJIq0BChFTb3VyY2VTeW5jRmFpbHVyZRI5CgZyZWFzb24YASABKA4yKS5zYWFzLmFjY291bnRzLnYxLlNvdXJjZVN5bmNGYWlsdXJlUmVhc29uEgwKBGNvZGUYAiABKAkSDwoHbWVzc2FnZRgDIAEoCRIQCghyZXRyeWluZxgEIAEoCBIsCghyZXRyeV9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAihAQKElNvdXJjZVN5bmNQcm9ncmVzcxIwCgVwaGFzZRgBIAEoDjIhLnNhYXMuYWNjb3VudHMudjEuU291cmNlU3luY1BoYXNlEjQKB3RyaWdnZXIYAiABKA4yIy5zYWFzLmFjY291bnRzLnYxLlNvdXJjZVN5bmNUcmlnZ2VyEi0KCXF1ZXVlZF9hdBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLwoLZmV0Y2hpbmdfYXQYBCABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi8KC2NvbXBpbGVkX2F0GAUgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIxCg1oYW5kZWRfb2ZmX2F0GAYgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIvCgtmaW5pc2hlZF9hdBgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASNAoHY2hhbmdlcxgIIAEoCzIjLnNhYXMuYWNjb3VudHMudjEuU291cmNlU3luY0NoYW5nZXMSNAoHZmFpbHVyZRgJIAEoCzIjLnNhYXMuYWNjb3VudHMudjEuU291cmNlU3luY0ZhaWx1cmUSDwoHYXR0ZW1wdBgKIAEoDRIUCgxtYXhfYXR0ZW1wdHMYCyABKA0i0gEKFUdldFNvdXJjZVN5bmNSZXNwb25zZRIYCgZqb2JfaWQYASABKAlCCLpIBXIDsAEBEi0KBXN0YXRlGAIgASgOMhYuc2Fhcy5qb2JzLnYxLkpvYlN0YXRlQga6SAPIAQESOAoKZGVsaXZlcmllcxgDIAMoCzIkLnNhYXMuYWNjb3VudHMudjEuU291cmNlU3luY0RlbGl2ZXJ5EjYKCHByb2dyZXNzGAQgASgLMiQuc2Fhcy5hY2NvdW50cy52MS5Tb3VyY2VTeW5jUHJvZ3Jlc3MiRQoTRGVsZXRlU291cmNlUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhQKAmlkGAIgASgJQgi6SAVyA7ABASIWChREZWxldGVTb3VyY2VSZXNwb25zZSJWChNHaXRIdWJBcHBSZXBvc2l0b3J5EgwKBHJlcG8YASABKAkSFgoOZGVmYXVsdF9icmFuY2gYAiABKAkSGQoRYWxyZWFkeV9jb25uZWN0ZWQYAyABKAgiNgoaQmVnaW5HaXRIdWJBcHBTZXR1cFJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABASJxChtCZWdpbkdpdEh1YkFwcFNldHVwUmVzcG9uc2USEwoLaW5zdGFsbF91cmwYASABKAkSDQoFc3RhdGUYAiABKAkSLgoKZXhwaXJlc19hdBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAinAEKHUNvbXBsZXRlR2l0SHViQXBwU2V0dXBSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGQoFc3RhdGUYAiABKAlCCrpIB3IFEAEY/wESLAoPaW5zdGFsbGF0aW9uX2lkGAMgASgJQhO6SBByDhABGCAyCF5bMC05XSskEhgKBGNvZGUYBCABKAlCCrpIB3IFEAEY/wEidgoeQ29tcGxldGVHaXRIdWJBcHBTZXR1cFJlc3BvbnNlEhcKD2luc3RhbGxhdGlvbl9pZBgBIAEoCRI7CgxyZXBvc2l0b3JpZXMYAiADKAsyJS5zYWFzLmFjY291bnRzLnYxLkdpdEh1YkFwcFJlcG9zaXRvcnkiUQofTWlncmF0ZUdpdEh1YlNvdXJjZVRvQXBwUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhQKAmlkGAIgASgJQgi6SAVyA7ABASJUCiBNaWdyYXRlR2l0SHViU291cmNlVG9BcHBSZXNwb25zZRIwCgpkYXRhc291cmNlGAEgASgLMhwuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlIsQBChVEYXRhc291cmNlQWNjb3VudExpbmsSCgoCaWQYASABKAkSDgoGb3JnX2lkGAIgASgJEg8KB3VzZXJfaWQYAyABKAkSEQoJY29ubmVjdG9yGAQgASgJEhsKE3Byb3ZpZGVyX2FjY291bnRfaWQYBSABKAkSHgoWcHJvdmlkZXJfYWNjb3VudF9sb2dpbhgGIAEoCRIuCgpjcmVhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCK3AQoWRGF0YXNvdXJjZUdyb3VwQmluZGluZxIKCgJpZBgBIAEoCRIOCgZvcmdfaWQYAiABKAkSEQoJY29ubmVjdG9yGAMgASgJEhkKEXByb3ZpZGVyX2dyb3VwX2lkGAQgASgJEg8KB3RlYW1faWQYBSABKAkSEgoKY3JlYXRlZF9ieRgGIAEoCRIuCgpjcmVhdGVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCKUAgoYRGF0YXNvdXJjZVZlcmlmaWVkRG9tYWluEgoKAmlkGAEgASgJEg4KBm9yZ19pZBgCIAEoCRIOCgZkb21haW4YAyABKAkSOAoGc3RhdHVzGAQgASgOMiguc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlRG9tYWluU3RhdHVzEhcKD3R4dF9yZWNvcmRfbmFtZRgFIAEoCRIYChB0eHRfcmVjb3JkX3ZhbHVlGAYgASgJEi8KC3ZlcmlmaWVkX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIuCgpjcmVhdGVkX2F0GAggASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcCIzChdEYXRhc291cmNlRGlyZWN0b3J5VGVhbRIKCgJpZBgBIAEoCRIMCgRuYW1lGAIgASgJIqYBCiFCZWdpbkRhdGFzb3VyY2VBY2NvdW50TGlua1JlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARI0Cgljb25uZWN0b3IYAiABKAlCIbpIHnIcEAIYIDIWXlthLXpdW2EtejAtOS1dezEsMzF9JBIeCgxyZWRpcmVjdF91cmkYAyABKAlCCLpIBXIDGIAQEhEKCXNvdXJjZV9pZBgEIAEoCSJ6CiJCZWdpbkRhdGFzb3VyY2VBY2NvdW50TGlua1Jlc3BvbnNlEhUKDWF1dGhvcml6ZV91cmwYASABKAkSDQoFc3RhdGUYAiABKAkSLgoKZXhwaXJlc19hdBgDIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAidQokQ29tcGxldGVEYXRhc291cmNlQWNjb3VudExpbmtSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGQoFc3RhdGUYAiABKAlCCrpIB3IFEAEYgEASGAoEY29kZRgDIAEoCUIKukgHcgUQARj/ASJeCiVDb21wbGV0ZURhdGFzb3VyY2VBY2NvdW50TGlua1Jlc3BvbnNlEjUKBGxpbmsYASABKAsyJy5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VBY2NvdW50TGluayI/CiNMaXN0TXlEYXRhc291cmNlQWNjb3VudExpbmtzUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBIl4KJExpc3RNeURhdGFzb3VyY2VBY2NvdW50TGlua3NSZXNwb25zZRI2CgVsaW5rcxgBIAMoCzInLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZUFjY291bnRMaW5rIlQKIkRlbGV0ZURhdGFzb3VyY2VBY2NvdW50TGlua1JlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIUCgJpZBgCIAEoCUIIukgFcgOwAQEiJQojRGVsZXRlRGF0YXNvdXJjZUFjY291bnRMaW5rUmVzcG9uc2UiOQodR2V0RGF0YXNvdXJjZURpcmVjdG9yeVJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABASKLAgoeR2V0RGF0YXNvdXJjZURpcmVjdG9yeVJlc3BvbnNlEjYKBWxpbmtzGAEgAygLMicuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlQWNjb3VudExpbmsSOgoIYmluZGluZ3MYAiADKAsyKC5zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VHcm91cEJpbmRpbmcSOwoHZG9tYWlucxgDIAMoCzIqLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVZlcmlmaWVkRG9tYWluEjgKBXRlYW1zGAQgAygLMikuc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlRGlyZWN0b3J5VGVhbSKuAQoaQmluZERhdGFzb3VyY2VHcm91cFJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARI0Cgljb25uZWN0b3IYAiABKAlCIbpIHnIcEAIYIDIWXlthLXpdW2EtejAtOS1dezEsMzF9JBIlChFwcm92aWRlcl9ncm91cF9pZBgDIAEoCUIKukgHcgUQARj/ARIZCgd0ZWFtX2lkGAQgASgJQgi6SAVyA7ABASJYChtCaW5kRGF0YXNvdXJjZUdyb3VwUmVzcG9uc2USOQoHYmluZGluZxgBIAEoCzIoLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZUdyb3VwQmluZGluZyJOChxVbmJpbmREYXRhc291cmNlR3JvdXBSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESFAoCaWQYAiABKAlCCLpIBXIDsAEBIh8KHVVuYmluZERhdGFzb3VyY2VHcm91cFJlc3BvbnNlIlQKHENsYWltRGF0YXNvdXJjZURvbWFpblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIaCgZkb21haW4YAiABKAlCCrpIB3IFEAMY/QEiWwodQ2xhaW1EYXRhc291cmNlRG9tYWluUmVzcG9uc2USOgoGZG9tYWluGAEgASgLMiouc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlVmVyaWZpZWREb21haW4iTwodVmVyaWZ5RGF0YXNvdXJjZURvbWFpblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIUCgJpZBgCIAEoCUIIukgFcgOwAQEiXAoeVmVyaWZ5RGF0YXNvdXJjZURvbWFpblJlc3BvbnNlEjoKBmRvbWFpbhgBIAEoCzIqLnNhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVZlcmlmaWVkRG9tYWluIk8KHURlbGV0ZURhdGFzb3VyY2VEb21haW5SZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESFAoCaWQYAiABKAlCCLpIBXIDsAEBIiAKHkRlbGV0ZURhdGFzb3VyY2VEb21haW5SZXNwb25zZSKeAgoQU291cmNlRGVsZWdhdGlvbhIKCgJpZBgBIAEoCRIRCglzb3VyY2VfaWQYAiABKAkSFAoMcHJpbmNpcGFsX2lkGAMgASgJEg4KBm1vZHVsZRgEIAEoCRIPCgdiaW5kaW5nGAUgASgJEi4KCmNyZWF0ZWRfYXQYBiABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEi4KCnJldm9rZWRfYXQYByABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEkAKCnJldm9jYXRpb24YCCABKA4yLC5zYWFzLmFjY291bnRzLnYxLlNvdXJjZURlbGVnYXRpb25SZXZvY2F0aW9uEhIKCnJldm9rZWRfYnkYCSABKAkigQEKHExpc3RTb3VyY2VEZWxlZ2F0aW9uc1JlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIgCglzb3VyY2VfaWQYAiABKAlCCLpIBXIDsAEBSACIAQESFwoPaW5jbHVkZV9yZXZva2VkGAMgASgIQgwKCl9zb3VyY2VfaWQiWAodTGlzdFNvdXJjZURlbGVnYXRpb25zUmVzcG9uc2USNwoLZGVsZWdhdGlvbnMYASADKAsyIi5zYWFzLmFjY291bnRzLnYxLlNvdXJjZURlbGVnYXRpb24iTwodUmV2b2tlU291cmNlRGVsZWdhdGlvblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIUCgJpZBgCIAEoCUIIukgFcgOwAQEiWAoeUmV2b2tlU291cmNlRGVsZWdhdGlvblJlc3BvbnNlEjYKCmRlbGVnYXRpb24YASABKAsyIi5zYWFzLmFjY291bnRzLnYxLlNvdXJjZURlbGVnYXRpb24iMgoPVHJpZ2dlcklkZW50aXR5Eg4KBnNvdXJjZRgBIAEoCRIPCgdiaW5kaW5nGAIgASgJIoQBChdEZWNsYXJlZFRyaWdnZXJTY2hlZHVsZRIMCgRjcm9uGAEgASgJEhAKCHRpbWV6b25lGAIgASgJEhIKCm1pc3NlZF9ydW4YAyABKAkSDwoHb3ZlcmxhcBgEIAEoCRIQCghkZWFkbGluZRgFIAEoAxISCgppbnB1dF9qc29uGAYgASgJIucCChpUcmlnZ2VyRGVsZWdhdGlvblJlZmVyZW5jZRIRCglyZWZlcmVuY2UYASABKAkSFQoNZGVsZWdhdGlvbl9pZBgCIAEoCRIOCgZtb2R1bGUYAyABKAkSDwoHYmluZGluZxgEIAEoCRIQCghhdWRpZW5jZRgFIAEoCRIOCgZ0ZW5hbnQYBiABKAkSGgoSb3duZXJfcHJpbmNpcGFsX2lkGAcgASgJEg4KBnNjb3BlcxgIIAMoCRIOCgZhY3RpdmUYCSABKAgSLgoKY3JlYXRlZF9hdBgKIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASLgoKcmV2b2tlZF9hdBgLIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASQAoKcmV2b2NhdGlvbhgMIAEoDjIsLnNhYXMuYWNjb3VudHMudjEuU291cmNlRGVsZWdhdGlvblJldm9jYXRpb24ivgEKE1RyaWdnZXJDaGlsZENvbnRleHQSFQoNZGVsZWdhdGlvbl9pZBgBIAEoCRIOCgZtb2R1bGUYAiABKAkSDwoHYmluZGluZxgDIAEoCRIQCghhdWRpZW5jZRgEIAEoCRIOCgZzY29wZXMYBSADKAkSDgoGbG9va3VwGAYgASgIEi0KCW1pbnRlZF9hdBgHIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASDgoGcmVjb3JkGAggASgJIs0CChhTb3VyY2VUcmlnZ2VyRGVjbGFyYXRpb24SDwoHcmVsZWFzZRgBIAEoCRIRCglvcGVyYXRpb24YAiABKAkSOwoIc2NoZWR1bGUYAyABKAsyKS5zYWFzLmFjY291bnRzLnYxLkRlY2xhcmVkVHJpZ2dlclNjaGVkdWxlEjMKCGlkZW50aXR5GAQgASgLMiEuc2Fhcy5hY2NvdW50cy52MS5UcmlnZ2VySWRlbnRpdHkSDgoGZGlnZXN0GAUgASgJEjYKEmxhc3Rfb2NjdXJyZW5jZV9hdBgGIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXASNgoSbmV4dF9vY2N1cnJlbmNlX2F0GAcgASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcBIbChN1bmRlY2xhcmFibGVfcmVhc29uGAggASgJIlYKHURlbGVnYXRlU291cmNlVHJpZ2dlcnNSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGwoJc291cmNlX2lkGAIgASgJQgi6SAVyA7ABASJjCh5EZWxlZ2F0ZVNvdXJjZVRyaWdnZXJzUmVzcG9uc2USQQoLZGVsZWdhdGlvbnMYASADKAsyLC5zYWFzLmFjY291bnRzLnYxLlRyaWdnZXJEZWxlZ2F0aW9uUmVmZXJlbmNlIlsKIkdldFNvdXJjZVRyaWdnZXJEZWNsYXJhdGlvblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIbCglzb3VyY2VfaWQYAiABKAlCCLpIBXIDsAEBIuIBCiNHZXRTb3VyY2VUcmlnZ2VyRGVjbGFyYXRpb25SZXNwb25zZRI/CgtkZWNsYXJhdGlvbhgBIAEoCzIqLnNhYXMuYWNjb3VudHMudjEuU291cmNlVHJpZ2dlckRlY2xhcmF0aW9uEkEKC2RlbGVnYXRpb25zGAIgAygLMiwuc2Fhcy5hY2NvdW50cy52MS5UcmlnZ2VyRGVsZWdhdGlvblJlZmVyZW5jZRI3CghjaGlsZHJlbhgDIAMoCzIlLnNhYXMuYWNjb3VudHMudjEuVHJpZ2dlckNoaWxkQ29udGV4dCJuChZSZWNvbmNpbGVTb3VyY2VSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGwoJc291cmNlX2lkGAIgASgJQgi6SAVyA7ABARIdCgllZmZlY3RfaWQYAyABKAlCCrpIB3IFEAEYgAEiTAoXUmVjb25jaWxlU291cmNlUmVzcG9uc2USEQoJZWZmZWN0X2lkGAEgASgJEg4KBnN0YXR1cxgCIAEoCRIOCgZqb2JfaWQYAyABKAkiPQocTG9va3VwUmVjb25jaWxlU291cmNlUmVxdWVzdBIdCgllZmZlY3RfaWQYASABKAlCCrpIB3IFEAEYgAEi8QIKD1NvdXJjZU9wZXJhdGlvbhIMCgRuYW1lGAEgASgJEhMKC2Rlc2NyaXB0aW9uGAIgASgJEg4KBm1ldGhvZBgDIAEoCRIMCgRwYXRoGAQgASgJEg0KBXF1ZXJ5GAUgAygJEi0KDGlucHV0X3NjaGVtYRgGIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSLgoNb3V0cHV0X3NjaGVtYRgHIAEoCzIXLmdvb2dsZS5wcm90b2J1Zi5TdHJ1Y3QSOAoGZWZmZWN0GAggASgOMiguc2Fhcy5hY2NvdW50cy52MS5Tb3VyY2VPcGVyYXRpb24uRWZmZWN0EhgKEG1heF9vdXRwdXRfYnl0ZXMYCSABKA0SDgoGZGlnZXN0GAogASgJIksKBkVmZmVjdBIWChJFRkZFQ1RfVU5TUEVDSUZJRUQQABIUChBFRkZFQ1RfUkVBRF9PTkxZEAESEwoPRUZGRUNUX01VVEFUSU9OEAIijgEKHkRlY2xhcmVTb3VyY2VPcGVyYXRpb25zUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhsKCXNvdXJjZV9pZBgCIAEoCUIIukgFcgOwAQESNQoKb3BlcmF0aW9ucxgDIAMoCzIhLnNhYXMuYWNjb3VudHMudjEuU291cmNlT3BlcmF0aW9uIlgKH0RlY2xhcmVTb3VyY2VPcGVyYXRpb25zUmVzcG9uc2USNQoKb3BlcmF0aW9ucxgBIAMoCzIhLnNhYXMuYWNjb3VudHMudjEuU291cmNlT3BlcmF0aW9uIlQKG0xpc3RTb3VyY2VPcGVyYXRpb25zUmVxdWVzdBIYCgZvcmdfaWQYASABKAlCCLpIBXIDsAEBEhsKCXNvdXJjZV9pZBgCIAEoCUIIukgFcgOwAQEiVQocTGlzdFNvdXJjZU9wZXJhdGlvbnNSZXNwb25zZRI1CgpvcGVyYXRpb25zGAEgAygLMiEuc2Fhcy5hY2NvdW50cy52MS5Tb3VyY2VPcGVyYXRpb24irgIKHEludm9rZVNvdXJjZU9wZXJhdGlvblJlcXVlc3QSGAoGb3JnX2lkGAEgASgJQgi6SAVyA7ABARIbCglzb3VyY2VfaWQYAiABKAlCCLpIBXIDsAEBEjAKCW9wZXJhdGlvbhgDIAEoCUIdukgachgyFl5bYS16XVthLXowLTlfXXswLDYzfSQSHQoKaW5wdXRfanNvbhgEIAEoCUIJukgGcgQogIAEEhsKCWVmZmVjdF9pZBgFIAEoCUIIukgFcgMogAESSQobZXhwZWN0ZWRfZGVjbGFyYXRpb25fZGlnZXN0GAYgASgJQh+6SBxyGjIYXihzaGEyNTY6WzAtOWEtZl17NjR9KT8kSACIAQFCHgocX2V4cGVjdGVkX2RlY2xhcmF0aW9uX2RpZ2VzdCJ/ChZTb3VyY2VPcGVyYXRpb25SZWNlaXB0EhEKCWVmZmVjdF9pZBgBIAEoCRIUCgxjb21taXR0ZWRfYXQYAiABKAkSDgoGc3RhdHVzGAMgASgJEhcKD3Byb3ZpZGVyX3N0YXR1cxgEIAEoDRITCgtvdXRwdXRfanNvbhgFIAEoCSJvCh1JbnZva2VTb3VyY2VPcGVyYXRpb25SZXNwb25zZRITCgtvdXRwdXRfanNvbhgBIAEoCRI5CgdyZWNlaXB0GAIgASgLMiguc2Fhcy5hY2NvdW50cy52MS5Tb3VyY2VPcGVyYXRpb25SZWNlaXB0IkMKIkxvb2t1cEludm9rZVNvdXJjZU9wZXJhdGlvblJlcXVlc3QSHQoJZWZmZWN0X2lkGAEgASgJQgq6SAdyBRABKIABInkKI1BydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXF1ZXN0EhgKBm9yZ19pZBgBIAEoCUIIukgFcgOwAQESGwoJc291cmNlX2lkGAIgASgJQgi6SAVyA7ABARIbCgllZmZlY3RfaWQYAyABKAlCCLpIBXIDKIABIlsKJFBydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXNwb25zZRIRCgllZmZlY3RfaWQYASABKAkSEAoIcmVjZWlwdHMYAiABKAMSDgoGc3RhdHVzGAMgASgJIkoKKUxvb2t1cFBydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXF1ZXN0Eh0KCWVmZmVjdF9pZBgBIAEoCUIKukgHcgUQASiAASq3AQoSRGF0YXNvdXJjZVByb3ZpZGVyEiMKH0RBVEFTT1VSQ0VfUFJPVklERVJfVU5TUEVDSUZJRUQQABIeChpEQVRBU09VUkNFX1BST1ZJREVSX0dJVEhVQhABEhsKF0RBVEFTT1VSQ0VfUFJPVklERVJfQVBJEAISHwobREFUQVNPVVJDRV9QUk9WSURFUl9DUkFXTEVSEAMSHgoaREFUQVNPVVJDRV9QUk9WSURFUl9VUExPQUQQBCqRAQoQRGF0YXNvdXJjZVN0YXR1cxIhCh1EQVRBU09VUkNFX1NUQVRVU19VTlNQRUNJRklFRBAAEhwKGERBVEFTT1VSQ0VfU1RBVFVTX0FDVElWRRABEhwKGERBVEFTT1VSQ0VfU1RBVFVTX1BBVVNFRBACEh4KGkRBVEFTT1VSQ0VfU1RBVFVTX0RFR1JBREVEEAMqvAEKFkRhdGFzb3VyY2VMaXZlRGVsaXZlcnkSKAokREFUQVNPVVJDRV9MSVZFX0RFTElWRVJZX1VOU1BFQ0lGSUVEEAASIQodREFUQVNPVVJDRV9MSVZFX0RFTElWRVJZX05PTkUQARIrCidEQVRBU09VUkNFX0xJVkVfREVMSVZFUllfU09VUkNFX1dFQkhPT0sQAhIoCiREQVRBU09VUkNFX0xJVkVfREVMSVZFUllfQVBQX1dFQkhPT0sQAyrWAQoRQXBpQ3JlZGVudGlhbEtpbmQSIwofQVBJX0NSRURFTlRJQUxfS0lORF9VTlNQRUNJRklFRBAAEh4KGkFQSV9DUkVERU5USUFMX0tJTkRfQkVBUkVSEAESHQoZQVBJX0NSRURFTlRJQUxfS0lORF9CQVNJQxACEh4KGkFQSV9DUkVERU5USUFMX0tJTkRfSEVBREVSEAMSHQoZQVBJX0NSRURFTlRJQUxfS0lORF9RVUVSWRAEEh4KGkFQSV9DUkVERU5USUFMX0tJTkRfT0FVVEgyEAUqpgIKHERhdGFzb3VyY2VDb25uZWN0b3JJbnRlcmZhY2USLgoqREFUQVNPVVJDRV9DT05ORUNUT1JfSU5URVJGQUNFX1VOU1BFQ0lGSUVEEAASKAokREFUQVNPVVJDRV9DT05ORUNUT1JfSU5URVJGQUNFX0ZJTEVTEAESKAokREFUQVNPVVJDRV9DT05ORUNUT1JfSU5URVJGQUNFX1BBR0VTEAISKgomREFUQVNPVVJDRV9DT05ORUNUT1JfSU5URVJGQUNFX1JFQ09SRFMQAxIrCidEQVRBU09VUkNFX0NPTk5FQ1RPUl9JTlRFUkZBQ0VfTUVTU0FHRVMQBBIpCiVEQVRBU09VUkNFX0NPTk5FQ1RPUl9JTlRFUkZBQ0VfRVZFTlRTEAUq7AEKGERhdGFzb3VyY2VDcmVkZW50aWFsTW9kZRIqCiZEQVRBU09VUkNFX0NSRURFTlRJQUxfTU9ERV9VTlNQRUNJRklFRBAAEiMKH0RBVEFTT1VSQ0VfQ1JFREVOVElBTF9NT0RFX05PTkUQARImCiJEQVRBU09VUkNFX0NSRURFTlRJQUxfTU9ERV9PUkdfQVBQEAISKQolREFUQVNPVVJDRV9DUkVERU5USUFMX01PREVfVVNFUl9PQVVUSBADEiwKKERBVEFTT1VSQ0VfQ1JFREVOVElBTF9NT0RFX1NUQVRJQ19TRUNSRVQQBCqXAQoWRGF0YXNvdXJjZVJlYWRlcnNNb2RlbBIoCiREQVRBU09VUkNFX1JFQURFUlNfTU9ERUxfVU5TUEVDSUZJRUQQABIqCiZEQVRBU09VUkNFX1JFQURFUlNfTU9ERUxfU09VUkNFX1NDT1BFRBABEicKI0RBVEFTT1VSQ0VfUkVBREVSU19NT0RFTF9UUkFOU0xBVEVEEAIq7gEKD1NvdXJjZVN5bmNQaGFzZRIhCh1TT1VSQ0VfU1lOQ19QSEFTRV9VTlNQRUNJRklFRBAAEhwKGFNPVVJDRV9TWU5DX1BIQVNFX1FVRVVFRBABEh4KGlNPVVJDRV9TWU5DX1BIQVNFX0ZFVENISU5HEAISHgoaU09VUkNFX1NZTkNfUEhBU0VfQ09NUElMRUQQAxIgChxTT1VSQ0VfU1lOQ19QSEFTRV9IQU5ERURfT0ZGEAQSGgoWU09VUkNFX1NZTkNfUEhBU0VfRE9ORRAFEhwKGFNPVVJDRV9TWU5DX1BIQVNFX0ZBSUxFRBAGKqYDChdTb3VyY2VTeW5jRmFpbHVyZVJlYXNvbhIqCiZTT1VSQ0VfU1lOQ19GQUlMVVJFX1JFQVNPTl9VTlNQRUNJRklFRBAAEisKJ1NPVVJDRV9TWU5DX0ZBSUxVUkVfUkVBU09OX1JBVEVfTElNSVRFRBABEikKJVNPVVJDRV9TWU5DX0ZBSUxVUkVfUkVBU09OX0NSRURFTlRJQUwQAhIsCihTT1VSQ0VfU1lOQ19GQUlMVVJFX1JFQVNPTl9BQ0NFU1NfREVOSUVEEAMSKAokU09VUkNFX1NZTkNfRkFJTFVSRV9SRUFTT05fTk9UX0ZPVU5EEAQSKAokU09VUkNFX1NZTkNfRkFJTFVSRV9SRUFTT05fVE9PX0xBUkdFEAUSLworU09VUkNFX1NZTkNfRkFJTFVSRV9SRUFTT05fSE9TVF9VTkFWQUlMQUJMRRAGEiQKIFNPVVJDRV9TWU5DX0ZBSUxVUkVfUkVBU09OX09USEVSEAcSLgoqU09VUkNFX1NZTkNfRkFJTFVSRV9SRUFTT05fREVMSVZFUllfRkFJTEVEEAgqwgEKEVNvdXJjZVN5bmNUcmlnZ2VyEiMKH1NPVVJDRV9TWU5DX1RSSUdHRVJfVU5TUEVDSUZJRUQQABIeChpTT1VSQ0VfU1lOQ19UUklHR0VSX01BTlVBTBABEiEKHVNPVVJDRV9TWU5DX1RSSUdHRVJfU0NIRURVTEVEEAISHwobU09VUkNFX1NZTkNfVFJJR0dFUl9XRUJIT09LEAMSJAogU09VUkNFX1NZTkNfVFJJR0dFUl9SRUNPTkZJR1VSRUQQBCqPAQoWRGF0YXNvdXJjZURvbWFpblN0YXR1cxIoCiREQVRBU09VUkNFX0RPTUFJTl9TVEFUVVNfVU5TUEVDSUZJRUQQABIkCiBEQVRBU09VUkNFX0RPTUFJTl9TVEFUVVNfUEVORElORxABEiUKIURBVEFTT1VSQ0VfRE9NQUlOX1NUQVRVU19WRVJJRklFRBACKpUDChpTb3VyY2VEZWxlZ2F0aW9uUmV2b2NhdGlvbhIsCihTT1VSQ0VfREVMRUdBVElPTl9SRVZPQ0FUSU9OX1VOU1BFQ0lGSUVEEAASKAokU09VUkNFX0RFTEVHQVRJT05fUkVWT0NBVElPTl9SRVZPS0VEEAESKQolU09VUkNFX0RFTEVHQVRJT05fUkVWT0NBVElPTl9SRVBMQUNFRBACEi8KK1NPVVJDRV9ERUxFR0FUSU9OX1JFVk9DQVRJT05fU09VUkNFX0RFTEVURUQQAxIvCitTT1VSQ0VfREVMRUdBVElPTl9SRVZPQ0FUSU9OX01FTUJFUl9SRU1PVkVEEAQSMAosU09VUkNFX0RFTEVHQVRJT05fUkVWT0NBVElPTl9QRVJNSVNTSU9OX0xPU1QQBRIuCipTT1VSQ0VfREVMRUdBVElPTl9SRVZPQ0FUSU9OX1VTRVJfSU5BQ1RJVkUQBhIwCixTT1VSQ0VfREVMRUdBVElPTl9SRVZPQ0FUSU9OX0JJTkRJTkdfQ0hBTkdFRBAHMtc7ChFEYXRhc291cmNlU2VydmljZRK5AwocUHJ1bmVTb3VyY2VPcGVyYXRpb25SZWNlaXB0cxI1LnNhYXMuYWNjb3VudHMudjEuUHJ1bmVTb3VyY2VPcGVyYXRpb25SZWNlaXB0c1JlcXVlc3QaNi5zYWFzLmFjY291bnRzLnYxLlBydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXNwb25zZSKpAsLzGGwIAhADGhFkYXRhc291cmNlOmludm9rZSIRZGF0YXNvdXJjZTppbnZva2UqDwoJc291cmNlX2lkEAQYAzABOiMKH3NhYXMuZGF0YXNvdXJjZS5yZWNlaXB0cy5wcnVuZWQQAkADSARQA1gDYAHa8xi0AQoCCDwSAwjYBBgFIgIIAioLVU5BVkFJTEFCTEUqElJFU09VUkNFX0VYSEFVU1RFRCoHQUJPUlRFRDIPc2Fhcy1kYXRhc291cmNlSkYvc2Fhcy5hY2NvdW50cy52MS5EYXRhc291cmNlU2VydmljZS9Mb29rdXBQcnVuZVNvdXJjZU9wZXJhdGlvblJlY2VpcHRzUIAgWIAgYAFyGAoGc291cmNlEgZpbnZva2USBHJlYWQYARLmAQoiTG9va3VwUHJ1bmVTb3VyY2VPcGVyYXRpb25SZWNlaXB0cxI7LnNhYXMuYWNjb3VudHMudjEuTG9va3VwUHJ1bmVTb3VyY2VPcGVyYXRpb25SZWNlaXB0c1JlcXVlc3QaNi5zYWFzLmFjY291bnRzLnYxLlBydW5lU291cmNlT3BlcmF0aW9uUmVjZWlwdHNSZXNwb25zZSJLwvMYRwgCEAMaD2RhdGFzb3VyY2U6cmVhZCIPZGF0YXNvdXJjZTpyZWFkKg8KCWVmZmVjdF9pZBAEGAMwAToCEAFAAUgDUANYA2ABEvwDChVJbnZva2VTb3VyY2VPcGVyYXRpb24SLi5zYWFzLmFjY291bnRzLnYxLkludm9rZVNvdXJjZU9wZXJhdGlvblJlcXVlc3QaLy5zYWFzLmFjY291bnRzLnYxLkludm9rZVNvdXJjZU9wZXJhdGlvblJlc3BvbnNlIoEDwvMYbggCEAMaEWRhdGFzb3VyY2U6aW52b2tlIhFkYXRhc291cmNlOmludm9rZSoPCglzb3VyY2VfaWQQBBgDMAE6JQohc2Fhcy5kYXRhc291cmNlLm9wZXJhdGlvbi5pbnZva2VkEARAA0gEUARYBGAB2vMYrwEKAgg8EgMI2AQYBSICCAIqC1VOQVZBSUxBQkxFKhJSRVNPVVJDRV9FWEhBVVNURUQqB0FCT1JURUQyD3NhYXMtZGF0YXNvdXJjZUo/L3NhYXMuYWNjb3VudHMudjEuRGF0YXNvdXJjZVNlcnZpY2UvTG9va3VwSW52b2tlU291cmNlT3BlcmF0aW9uUICABFiAgEBgAXIYCgZzb3VyY2USBmludm9rZRIEcmVhZBgBgtPkkwJVOgEqIlAvdjEvb3JnYW5pemF0aW9ucy97b3JnX2lkfS9kYXRhc291cmNlcy97c291cmNlX2lkfS9vcGVyYXRpb25zL3tvcGVyYXRpb259Omludm9rZRKHAgobTG9va3VwSW52b2tlU291cmNlT3BlcmF0aW9uEjQuc2Fhcy5hY2NvdW50cy52MS5Mb29rdXBJbnZva2VTb3VyY2VPcGVyYXRpb25SZXF1ZXN0Gi8uc2Fhcy5hY2NvdW50cy52MS5JbnZva2VTb3VyY2VPcGVyYXRpb25SZXNwb25zZSKAAcLzGEcIAhADGg9kYXRhc291cmNlOnJlYWQiD2RhdGFzb3VyY2U6cmVhZCoPCgllZmZlY3RfaWQQBBgDMAE6AhABQAFIA1ADWARgAYLT5JMCLxItL3YxL2RhdGFzb3VyY2Utb3BlcmF0aW9uLXJlY2VpcHRzL3tlZmZlY3RfaWR9EqMCChxWZXJpZnlTb3VyY2VPcGVyYXRpb25SZWNlaXB0Ei4uc2Fhcy5hY2NvdW50cy52MS5JbnZva2VTb3VyY2VPcGVyYXRpb25SZXF1ZXN0Gi8uc2Fhcy5hY2NvdW50cy52MS5JbnZva2VTb3VyY2VPcGVyYXRpb25SZXNwb25zZSKhAcLzGGoIAhADGg9kYXRhc291cmNlOnJlYWQiD2RhdGFzb3VyY2U6cmVhZCoPCgllZmZlY3RfaWQQBBgDMAE6JQohc2Fhcy5kYXRhc291cmNlLm9wZXJhdGlvbi5pbnZva2VkEANAAUgDUARYBGABgtPkkwItOgEqIigvdjEvZGF0YXNvdXJjZS1vcGVyYXRpb24tcmVjZWlwdHMvdmVyaWZ5EoUCChRMaXN0U291cmNlT3BlcmF0aW9ucxItLnNhYXMuYWNjb3VudHMudjEuTGlzdFNvdXJjZU9wZXJhdGlvbnNSZXF1ZXN0Gi4uc2Fhcy5hY2NvdW50cy52MS5MaXN0U291cmNlT3BlcmF0aW9uc1Jlc3BvbnNlIo0BwvMYRAgCEAMaD2RhdGFzb3VyY2U6cmVhZCIPZGF0YXNvdXJjZTpyZWFkKgwKBm9yZ19pZBACGAEwAToCEAFAAUgDUANYA2ABgtPkkwI/Ej0vdjEvb3JnYW5pemF0aW9ucy97b3JnX2lkfS9kYXRhc291cmNlcy97c291cmNlX2lkfS9vcGVyYXRpb25zEpMCChdEZWNsYXJlU291cmNlT3BlcmF0aW9ucxIwLnNhYXMuYWNjb3VudHMudjEuRGVjbGFyZVNvdXJjZU9wZXJhdGlvbnNSZXF1ZXN0GjEuc2Fhcy5hY2NvdW50cy52MS5EZWNsYXJlU291cmNlT3BlcmF0aW9uc1Jlc3BvbnNlIpIBwvMYRggCEAQqDAoGb3JnX2lkEAIYATABOiYKInNhYXMuZGF0YXNvdXJjZS5vcGVyYXRpb24uZGVjbGFyZWQQAkABSARQA1gDYAGC0+STAkI6ASoiPS92MS9vcmdhbml6YXRpb25zL3tvcmdfaWR9L2RhdGFzb3VyY2VzL3tzb3VyY2VfaWR9L29wZXJhdGlvbnMSrAEKD0FkZEdpdEh1YlNvdXJjZRIoLnNhYXMuYWNjb3VudHMudjEuQWRkR2l0SHViU291cmNlUmVxdWVzdBopLnNhYXMuYWNjb3VudHMudjEuQWRkR2l0SHViU291cmNlUmVzcG9uc2UiRMLzGEAIAhAEKgwKBm9yZ19pZBACGAEwATogChxzYWFzLmRhdGFzb3VyY2Uuc291cmNlLmFkZGVkEAJAAUgEUARYA2ABEpoBCglBZGRTb3VyY2USIi5zYWFzLmFjY291bnRzLnYxLkFkZFNvdXJjZVJlcXVlc3QaIy5zYWFzLmFjY291bnRzLnYxLkFkZFNvdXJjZVJlc3BvbnNlIkTC8xhACAIQBCoMCgZvcmdfaWQQAhgBMAE6IAocc2Fhcy5kYXRhc291cmNlLnNvdXJjZS5hZGRlZBACQAFIBFAEWANgARKlAQoMVXBkYXRlU291cmNlEiUuc2Fhcy5hY2NvdW50cy52MS5VcGRhdGVTb3VyY2VSZXF1ZXN0GiYuc2Fhcy5hY2NvdW50cy52MS5VcGRhdGVTb3VyY2VSZXNwb25zZSJGwvMYQggCEAQqDAoGb3JnX2lkEAIYATABOiIKHnNhYXMuZGF0YXNvdXJjZS5zb3VyY2UudXBkYXRlZBACQAFIBFACWANgARKPAQoUR2V0RGF0YXNvdXJjZUNhdGFsb2cSLS5zYWFzLmFjY291bnRzLnYxLkdldERhdGFzb3VyY2VDYXRhbG9nUmVxdWVzdBouLnNhYXMuYWNjb3VudHMudjEuR2V0RGF0YXNvdXJjZUNhdGFsb2dSZXNwb25zZSIYwvMYFAgCEAEwAToCEAFAAUgDUAJYAmABEoIBCgtMaXN0U291cmNlcxIkLnNhYXMuYWNjb3VudHMudjEuTGlzdFNvdXJjZXNSZXF1ZXN0GiUuc2Fhcy5hY2NvdW50cy52MS5MaXN0U291cmNlc1Jlc3BvbnNlIibC8xgiCAIQAyoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARJ8CglHZXRTb3VyY2USIi5zYWFzLmFjY291bnRzLnYxLkdldFNvdXJjZVJlcXVlc3QaIy5zYWFzLmFjY291bnRzLnYxLkdldFNvdXJjZVJlc3BvbnNlIibC8xgiCAIQAyoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARKeAQoKU3luY1NvdXJjZRIjLnNhYXMuYWNjb3VudHMudjEuU3luY1NvdXJjZVJlcXVlc3QaJC5zYWFzLmFjY291bnRzLnYxLlN5bmNTb3VyY2VSZXNwb25zZSJFwvMYQQgCEAQqDAoGb3JnX2lkEAIYATABOiEKHXNhYXMuZGF0YXNvdXJjZS5zb3VyY2Uuc3luY2VkEAJAAUgEUARYAmABEogBCg1HZXRTb3VyY2VTeW5jEiYuc2Fhcy5hY2NvdW50cy52MS5HZXRTb3VyY2VTeW5jUmVxdWVzdBonLnNhYXMuYWNjb3VudHMudjEuR2V0U291cmNlU3luY1Jlc3BvbnNlIibC8xgiCAIQBCoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARKlAQoMRGVsZXRlU291cmNlEiUuc2Fhcy5hY2NvdW50cy52MS5EZWxldGVTb3VyY2VSZXF1ZXN0GiYuc2Fhcy5hY2NvdW50cy52MS5EZWxldGVTb3VyY2VSZXNwb25zZSJGwvMYQggCEAQqDAoGb3JnX2lkEAIYATABOiIKHnNhYXMuZGF0YXNvdXJjZS5zb3VyY2UucmVtb3ZlZBACQAFIBFACWAJgARLEAQoTQmVnaW5HaXRIdWJBcHBTZXR1cBIsLnNhYXMuYWNjb3VudHMudjEuQmVnaW5HaXRIdWJBcHBTZXR1cFJlcXVlc3QaLS5zYWFzLmFjY291bnRzLnYxLkJlZ2luR2l0SHViQXBwU2V0dXBSZXNwb25zZSJQwvMYTAgCEAQqDAoGb3JnX2lkEAIYATABOiwKKHNhYXMuZGF0YXNvdXJjZS5naXRodWJfYXBwLnNldHVwX3N0YXJ0ZWQQAkABSARQAlgEYAESzwEKFkNvbXBsZXRlR2l0SHViQXBwU2V0dXASLy5zYWFzLmFjY291bnRzLnYxLkNvbXBsZXRlR2l0SHViQXBwU2V0dXBSZXF1ZXN0GjAuc2Fhcy5hY2NvdW50cy52MS5Db21wbGV0ZUdpdEh1YkFwcFNldHVwUmVzcG9uc2UiUsLzGE4IAhAEKgwKBm9yZ19pZBACGAEwATouCipzYWFzLmRhdGFzb3VyY2UuZ2l0aHViX2FwcC5zZXR1cF9jb21wbGV0ZWQQAkABSARQBFgDYAESzQEKGE1pZ3JhdGVHaXRIdWJTb3VyY2VUb0FwcBIxLnNhYXMuYWNjb3VudHMudjEuTWlncmF0ZUdpdEh1YlNvdXJjZVRvQXBwUmVxdWVzdBoyLnNhYXMuYWNjb3VudHMudjEuTWlncmF0ZUdpdEh1YlNvdXJjZVRvQXBwUmVzcG9uc2UiSsLzGEYIAhAEKgwKBm9yZ19pZBACGAEwATomCiJzYWFzLmRhdGFzb3VyY2UuY3JlZGVudGlhbC51cGRhdGVkEAJAAUgEUAJYA2ABEtUBChpCZWdpbkRhdGFzb3VyY2VBY2NvdW50TGluaxIzLnNhYXMuYWNjb3VudHMudjEuQmVnaW5EYXRhc291cmNlQWNjb3VudExpbmtSZXF1ZXN0GjQuc2Fhcy5hY2NvdW50cy52MS5CZWdpbkRhdGFzb3VyY2VBY2NvdW50TGlua1Jlc3BvbnNlIkzC8xhICAIQAyoMCgZvcmdfaWQQAhgBMAE6KAokc2Fhcy5kYXRhc291cmNlLmFjY291bnRfbGlua19zdGFydGVkEAJAAUgEUAJYBGABEtgBCh1Db21wbGV0ZURhdGFzb3VyY2VBY2NvdW50TGluaxI2LnNhYXMuYWNjb3VudHMudjEuQ29tcGxldGVEYXRhc291cmNlQWNjb3VudExpbmtSZXF1ZXN0Gjcuc2Fhcy5hY2NvdW50cy52MS5Db21wbGV0ZURhdGFzb3VyY2VBY2NvdW50TGlua1Jlc3BvbnNlIkbC8xhCCAIQAyoMCgZvcmdfaWQQAhgBMAE6Igoec2Fhcy5kYXRhc291cmNlLmFjY291bnRfbGlua2VkEAJAAUgEUARYA2ABErUBChxMaXN0TXlEYXRhc291cmNlQWNjb3VudExpbmtzEjUuc2Fhcy5hY2NvdW50cy52MS5MaXN0TXlEYXRhc291cmNlQWNjb3VudExpbmtzUmVxdWVzdBo2LnNhYXMuYWNjb3VudHMudjEuTGlzdE15RGF0YXNvdXJjZUFjY291bnRMaW5rc1Jlc3BvbnNlIibC8xgiCAIQAyoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARLUAQobRGVsZXRlRGF0YXNvdXJjZUFjY291bnRMaW5rEjQuc2Fhcy5hY2NvdW50cy52MS5EZWxldGVEYXRhc291cmNlQWNjb3VudExpbmtSZXF1ZXN0GjUuc2Fhcy5hY2NvdW50cy52MS5EZWxldGVEYXRhc291cmNlQWNjb3VudExpbmtSZXNwb25zZSJIwvMYRAgCEAMqDAoGb3JnX2lkEAIYATABOiQKIHNhYXMuZGF0YXNvdXJjZS5hY2NvdW50X3VubGlua2VkEAJAAUgEUAJYA2ABEqMBChZHZXREYXRhc291cmNlRGlyZWN0b3J5Ei8uc2Fhcy5hY2NvdW50cy52MS5HZXREYXRhc291cmNlRGlyZWN0b3J5UmVxdWVzdBowLnNhYXMuYWNjb3VudHMudjEuR2V0RGF0YXNvdXJjZURpcmVjdG9yeVJlc3BvbnNlIibC8xgiCAIQBCoMCgZvcmdfaWQQAhgBMAE6AhABQAFIA1ACWANgARK3AQoTQmluZERhdGFzb3VyY2VHcm91cBIsLnNhYXMuYWNjb3VudHMudjEuQmluZERhdGFzb3VyY2VHcm91cFJlcXVlc3QaLS5zYWFzLmFjY291bnRzLnYxLkJpbmREYXRhc291cmNlR3JvdXBSZXNwb25zZSJDwvMYPwgCEAQqDAoGb3JnX2lkEAIYATABOh8KG3NhYXMuZGF0YXNvdXJjZS5ncm91cF9ib3VuZBACQAFIBFACWANgARK/AQoVVW5iaW5kRGF0YXNvdXJjZUdyb3VwEi4uc2Fhcy5hY2NvdW50cy52MS5VbmJpbmREYXRhc291cmNlR3JvdXBSZXF1ZXN0Gi8uc2Fhcy5hY2NvdW50cy52MS5VbmJpbmREYXRhc291cmNlR3JvdXBSZXNwb25zZSJFwvMYQQgCEAQqDAoGb3JnX2lkEAIYATABOiEKHXNhYXMuZGF0YXNvdXJjZS5ncm91cF91bmJvdW5kEAJAAUgEUAJYA2ABEsABChVDbGFpbURhdGFzb3VyY2VEb21haW4SLi5zYWFzLmFjY291bnRzLnYxLkNsYWltRGF0YXNvdXJjZURvbWFpblJlcXVlc3QaLy5zYWFzLmFjY291bnRzLnYxLkNsYWltRGF0YXNvdXJjZURvbWFpblJlc3BvbnNlIkbC8xhCCAIQBCoMCgZvcmdfaWQQAhgBMAE6Igoec2Fhcy5kYXRhc291cmNlLmRvbWFpbl9jbGFpbWVkEAJAAUgEUAJYA2ABEsQBChZWZXJpZnlEYXRhc291cmNlRG9tYWluEi8uc2Fhcy5hY2NvdW50cy52MS5WZXJpZnlEYXRhc291cmNlRG9tYWluUmVxdWVzdBowLnNhYXMuYWNjb3VudHMudjEuVmVyaWZ5RGF0YXNvdXJjZURvbWFpblJlc3BvbnNlIkfC8xhDCAIQBCoMCgZvcmdfaWQQAhgBMAE6Iwofc2Fhcy5kYXRhc291cmNlLmRvbWFpbl92ZXJpZmllZBACQAFIBFACWANgARLDAQoWRGVsZXRlRGF0YXNvdXJjZURvbWFpbhIvLnNhYXMuYWNjb3VudHMudjEuRGVsZXRlRGF0YXNvdXJjZURvbWFpblJlcXVlc3QaMC5zYWFzLmFjY291bnRzLnYxLkRlbGV0ZURhdGFzb3VyY2VEb21haW5SZXNwb25zZSJGwvMYQggCEAQqDAoGb3JnX2lkEAIYATABOiIKHnNhYXMuZGF0YXNvdXJjZS5kb21haW5fcmVtb3ZlZBACQAFIBFACWANgARKgAQoVTGlzdFNvdXJjZURlbGVnYXRpb25zEi4uc2Fhcy5hY2NvdW50cy52MS5MaXN0U291cmNlRGVsZWdhdGlvbnNSZXF1ZXN0Gi8uc2Fhcy5hY2NvdW50cy52MS5MaXN0U291cmNlRGVsZWdhdGlvbnNSZXNwb25zZSImwvMYIggCEAQqDAoGb3JnX2lkEAIYATABOgIQAUABSANQAlgDYAESxwEKFlJldm9rZVNvdXJjZURlbGVnYXRpb24SLy5zYWFzLmFjY291bnRzLnYxLlJldm9rZVNvdXJjZURlbGVnYXRpb25SZXF1ZXN0GjAuc2Fhcy5hY2NvdW50cy52MS5SZXZva2VTb3VyY2VEZWxlZ2F0aW9uUmVzcG9uc2UiSsLzGEYIAhAEKgwKBm9yZ19pZBACGAEwATomCiJzYWFzLmRhdGFzb3VyY2UuZGVsZWdhdGlvbi5yZXZva2VkEAJAAUgEUAJYA2ABEusBChZEZWxlZ2F0ZVNvdXJjZVRyaWdnZXJzEi8uc2Fhcy5hY2NvdW50cy52MS5EZWxlZ2F0ZVNvdXJjZVRyaWdnZXJzUmVxdWVzdBowLnNhYXMuYWNjb3VudHMudjEuRGVsZWdhdGVTb3VyY2VUcmlnZ2Vyc1Jlc3BvbnNlIm7C8xhqCAIQBCoMCgZvcmdfaWQQAhgBMAE6Sgoic2Fhcy5kYXRhc291cmNlLmRlbGVnYXRpb24uY3JlYXRlZAoic2Fhcy5kYXRhc291cmNlLmRlbGVnYXRpb24ucmV2b2tlZBACQAFIBFACWANgARKyAQobR2V0U291cmNlVHJpZ2dlckRlY2xhcmF0aW9uEjQuc2Fhcy5hY2NvdW50cy52MS5HZXRTb3VyY2VUcmlnZ2VyRGVjbGFyYXRpb25SZXF1ZXN0GjUuc2Fhcy5hY2NvdW50cy52MS5HZXRTb3VyY2VUcmlnZ2VyRGVjbGFyYXRpb25SZXNwb25zZSImwvMYIggCEAQqDAoGb3JnX2lkEAIYATABOgIQAUABSANQAlgDYAESgwMKD1JlY29uY2lsZVNvdXJjZRIoLnNhYXMuYWNjb3VudHMudjEuUmVjb25jaWxlU291cmNlUmVxdWVzdBopLnNhYXMuYWNjb3VudHMudjEuUmVjb25jaWxlU291cmNlUmVzcG9uc2UimgLC8xhqCAIQAxoRZGF0YXNvdXJjZTppbnZva2UiEWRhdGFzb3VyY2U6aW52b2tlKg8KCXNvdXJjZV9pZBAEGAMwATohCh1zYWFzLmRhdGFzb3VyY2Uuc291cmNlLnN5bmNlZBACQANIBFACWAJgAdrzGKcBCgIIPBIDCNgEGAUiAggCKgtVTkFWQUlMQUJMRSoSUkVTT1VSQ0VfRVhIQVVTVEVEKgdBQk9SVEVEMg9zYWFzLWRhdGFzb3VyY2VKOS9zYWFzLmFjY291bnRzLnYxLkRhdGFzb3VyY2VTZXJ2aWNlL0xvb2t1cFJlY29uY2lsZVNvdXJjZVCAIFiAIGABchgKBnNvdXJjZRIGaW52b2tlEgRyZWFkGAESvwEKFUxvb2t1cFJlY29uY2lsZVNvdXJjZRIuLnNhYXMuYWNjb3VudHMudjEuTG9va3VwUmVjb25jaWxlU291cmNlUmVxdWVzdBopLnNhYXMuYWNjb3VudHMudjEuUmVjb25jaWxlU291cmNlUmVzcG9uc2UiS8LzGEcIAhADGg9kYXRhc291cmNlOnJlYWQiD2RhdGFzb3VyY2U6cmVhZCoPCgllZmZlY3RfaWQQBBgDMAE6AhABQAFIA1ACWAJgAUK3AQoUY29tLnNhYXMuYWNjb3VudHMudjFCD0RhdGFzb3VyY2VQcm90b1ABWixhY2NvdW50cy9wa2cvZ2VuL3NhYXMvYWNjb3VudHMvdjE7YWNjb3VudHN2MaICA1NBWKoCEFNhYXMuQWNjb3VudHMuVjHKAhBTYWFzXEFjY291bnRzXFYx4gIcU2Fhc1xBY2NvdW50c1xWMVxHUEJNZXRhZGF0YeoCElNhYXM6OkFjY291bnRzOjpWMWIGcHJvdG8z", [file_buf_validate_validate, file_codefly_runnable_v0_options, file_google_api_annotations, file_google_protobuf_duration, file_google_protobuf_struct, file_google_protobuf_timestamp, file_saas_jobs_v1_jobs, file_saas_policy_v1_options]); /** * GitHubDatasourceConfig is the public, non-secret configuration of a GitHub @@ -4227,6 +4227,23 @@ export const DatasourceService: GenService<{ input: typeof LookupInvokeSourceOperationRequestSchema; output: typeof InvokeSourceOperationResponseSchema; }, + /** + * VerifySourceOperationReceipt conditionally reads retained output using the + * exact original Invoke request, including a nonempty declaration precondition. + * Committed means the normalized request and admitted declaration match the + * stored attempt. Unknown remains unresolved; this method never redispatches. + * Current actor, tenant and stored-source read authority apply before comparison. + * Source-scope refusals emit the existing failure audit; successful reads do not + * record another invocation or change the retained receipt. + * This is independent of the paired Runnable Lookup contract above. + * + * @generated from rpc saas.accounts.v1.DatasourceService.VerifySourceOperationReceipt + */ + verifySourceOperationReceipt: { + methodKind: "unary"; + input: typeof InvokeSourceOperationRequestSchema; + output: typeof InvokeSourceOperationResponseSchema; + }, /** * ListSourceOperations reads the currently admitted declarations under source authority. *