From 417fb78d9a7a3353d57ec54fe89516084b60ebcd Mon Sep 17 00:00:00 2001 From: antoinetoussaint-byte Date: Tue, 20 Oct 2026 20:08:08 +0000 Subject: [PATCH 1/9] fix(accounts): warn mode never grants less than the envelope allows (#1186) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Under MODULE_AUTHORITY_ENFORCEMENT=warn the ceiling was built from the manifest's request alone, so a principal whose manifest publishes no contract — the runtime, whose operations are the installations prepared for it and exist only in the envelope — lost every binding the envelope granted it. On a validation cell every delegated exchange for plan-sync-pages was refused as "source delegation no longer authorizes this work" while the envelope named the binding and the person had just re-delegated. The warn ceiling now starts from the entry the envelope holds and widens it to what the manifest asks: a bare manifest is granted the envelope whole, exactly as enforce grants it, and a manifest that asks is held to what it asks, as before. Co-authored-by: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 --- module/configurations/README.md | 2 +- .../business/module_authority_enforcement.go | 44 +++++++++++++++---- .../module_authority_enforcement_test.go | 37 ++++++++++++++++ 3 files changed, 74 insertions(+), 9 deletions(-) diff --git a/module/configurations/README.md b/module/configurations/README.md index 62ecdf75a..4743ab3f0 100644 --- a/module/configurations/README.md +++ b/module/configurations/README.md @@ -54,7 +54,7 @@ the three services below, all of which need the origin. | `APP_BASE_URL` | accounts (links it mints, the OAuth issuer, the origin it compares a forwarded one with), auth-gateway (a solution's MCP protected-resource metadata and token audience), frontend (the verified public origin it presents to accounts and judges the solution proxy against) | local only: the frontend uses its loopback own endpoint and accounts trusts the forwarded origin. Outside local, accounts refuses to start and a deployed frontend refuses sign-in, naming the key | | `BOOTSTRAP_ADMIN_EMAIL` | accounts | no one-time bootstrap administrator | | `ORGANIZATION_CREATION` | accounts | `open`; `platform_admin` or `disabled` restrict it, anything else refuses the boot | -| `MODULE_AUTHORITY_ENFORCEMENT` | accounts | `enforce`; `warn` grants every module what its manifest asks for and logs, on every registry pass, what the envelope would have withheld (a cell under validation, never a production default — the token, workload, build, installation and tenant stay enforced); anything else refuses the boot | +| `MODULE_AUTHORITY_ENFORCEMENT` | accounts | `enforce`; `warn` grants every module everything its envelope entry allows, widened to what its manifest asks for, and logs, on every registry pass, what the envelope would have withheld (a cell under validation, never a production default — the token, workload, build, installation and tenant stay enforced); anything else refuses the boot | A deployed frontend never stands its own endpoint in for `APP_BASE_URL` when that endpoint is loopback, cluster-internal (`*.svc`, `*.cluster.local`, a diff --git a/module/services/accounts/code/pkg/business/module_authority_enforcement.go b/module/services/accounts/code/pkg/business/module_authority_enforcement.go index 058aba6c3..e33de5587 100644 --- a/module/services/accounts/code/pkg/business/module_authority_enforcement.go +++ b/module/services/accounts/code/pkg/business/module_authority_enforcement.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "maps" "sort" "strings" @@ -134,24 +135,33 @@ func sharedTenant(envelope *ModuleEnvelope) (string, bool) { return shared, shared != "" } -// requestedCeiling is the widest ceiling for a manifest: exactly what its -// authority block asks for, as the envelope would have to be written to allow -// all of it (the same derivation an administrator's generator performs), with -// the tenancy given. What the request cannot express — artifact policies and -// read audiences — is kept from the entry the envelope holds, if any. +// requestedCeiling is the widest ceiling for a manifest: everything the entry +// the envelope holds already allows, widened to exactly what the authority +// block asks for (the same derivation an administrator's generator performs), +// with the tenancy given. Warn never grants less than the envelope does: a +// binding only the envelope names — a runtime's installation bindings, which +// no manifest can express — stays granted, and a binding both name is granted +// as the manifest asks. What the request cannot express — artifact policies +// and read audiences — is likewise kept from the held entry. func requestedCeiling(request *solutionhost.Authority, held ModuleEnvelopePrincipal, tenant string, crossTenant bool) ModuleEnvelopePrincipal { grant := ModulePrincipalGrant{ ArtifactPolicies: held.Grant.ArtifactPolicies, ReadAudiences: held.Grant.ReadAudiences, Tenant: tenant, CrossTenant: crossTenant, + Queues: sortedCopy(held.Grant.Queues), + Namespaces: sortedCopy(held.Grant.Namespaces), + Resources: sortedCopy(held.Grant.Resources), + } + if len(held.Grant.OperationAudiences) > 0 { + grant.OperationAudiences = maps.Clone(held.Grant.OperationAudiences) } if request == nil { return ModuleEnvelopePrincipal{Grant: grant} } - grant.Queues = sortedCopy(request.Queues) - grant.Namespaces = sortedCopy(request.Namespaces) - grant.Resources = sortedCopy(request.ResourceKinds) + grant.Queues = sortedUnion(held.Grant.Queues, request.Queues) + grant.Namespaces = sortedUnion(held.Grant.Namespaces, request.Namespaces) + grant.Resources = sortedUnion(held.Grant.Resources, request.ResourceKinds) // A connect names one binding per module, so exactly one may accept // delegations: the one the envelope already chose, or the only binding // when the manifest declares one. With several and no choice, none does — @@ -218,6 +228,24 @@ func scopesOf(units []string) []ModuleOperationScope { return scopes } +// sortedUnion is every distinct value of both lists, sorted; nil when both +// are empty, like sortedCopy. +func sortedUnion(left, right []string) []string { + seen := map[string]bool{} + var out []string + for _, values := range [][]string{left, right} { + for _, value := range values { + if seen[value] { + continue + } + seen[value] = true + out = append(out, value) + } + } + sort.Strings(out) + return out +} + func sortedCopy(values []string) []string { if len(values) == 0 { return nil diff --git a/module/services/accounts/code/pkg/business/module_authority_enforcement_test.go b/module/services/accounts/code/pkg/business/module_authority_enforcement_test.go index 7540d6e97..f0a02897f 100644 --- a/module/services/accounts/code/pkg/business/module_authority_enforcement_test.go +++ b/module/services/accounts/code/pkg/business/module_authority_enforcement_test.go @@ -104,3 +104,40 @@ func TestParseModuleAuthorityEnforcement(t *testing.T) { _, err := ParseModuleAuthorityEnforcement("off") require.Error(t, err, "a typo cannot quietly turn enforcement off") } + +// The runtime's installation bindings exist only in the envelope: a runtime +// manifest declares no bindings, because the operations it is admitted to are +// a property of each installation, not of the module. Warn widens the envelope +// to the manifest; it never shrinks the envelope to it. +func TestWarnKeepsWhatOnlyTheEnvelopeGrants(t *testing.T) { + held := enforcementStaleEntry() + held.Grant.Namespaces = []string{"document"} + held.Grant.OperationAudiences["plan-sync-pages"] = ModuleOperationAudience{Audience: "documents", + InvokeScopes: []ModuleOperationScope{{ResourceKind: "documents.ingest", Actions: []string{"read", "write"}}}, + LookupScopes: []ModuleOperationScope{{ResourceKind: "documents.ingest", Actions: []string{"read"}}}} + + // A manifest that asks for nothing at all — the runtime's shape — is + // granted the envelope's entry whole, exactly as enforce grants it. + bare := &solutionhost.ModuleManifest{Binding: "host.local.runtime", Alias: "runtime", Generation: 1, + Authority: &solutionhost.Authority{Principal: "runtime"}} + enforced, _, err := deriveUnderEnforcement(ModuleAuthorityEnforce, "runtime", held, true, bare, []ModuleWorkload{testWorkload}, nil) + require.NoError(t, err) + grant, omitted, err := deriveUnderEnforcement(ModuleAuthorityWarn, "runtime", held, true, bare, []ModuleWorkload{testWorkload}, nil) + require.NoError(t, err) + require.Empty(t, omitted) + require.Equal(t, held.Grant.OperationAudiences, grant.OperationAudiences, "warn never grants less than the envelope allows") + require.Equal(t, enforced.OperationAudiences, grant.OperationAudiences, "and for a bare manifest it is exactly enforce's grant") + require.Equal(t, []string{"datasource"}, grant.Queues) + require.Equal(t, []string{"document"}, grant.Namespaces) + + // A manifest that asks is held to what it asks, as under enforce: the + // envelope-only binding is not granted on top of a published contract, and + // the manifest's own bindings are granted as asked. + grant, omitted, err = deriveUnderEnforcement(ModuleAuthorityWarn, "documents", held, true, + enforcementManifest(), []ModuleWorkload{testWorkload}, nil) + require.NoError(t, err) + require.Empty(t, omitted) + require.NotContains(t, grant.OperationAudiences, "plan-sync-pages") + require.Contains(t, grant.OperationAudiences, "runtime-admission") + require.Equal(t, []string{"documents", "documents.collection", "documents.ingest"}, grant.Resources) +} From 2cb00737ed56c79c823bc82f4055681a51919e7c Mon Sep 17 00:00:00 2001 From: antoinetoussaint-byte Date: Tue, 20 Oct 2026 20:25:52 +0000 Subject: [PATCH 2/9] A connected datasource is edited in place instead of deleted and re-added (#1183) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(accounts,frontend): a connected datasource is edited in place instead of deleted and re-added (#1181) Changing a source's branch or path filters meant deleting it — losing its sync cursor, its credential envelope and the consuming module's collection continuity — and adding it back. `UpdateSource` moves the three mutable fields on the same row. What it will not move is as deliberate as what it does: the repository, the provider, the boundary node and the credential are each the identity of something a consumer already holds, so none is reachable here. Re-aiming a source at another repository in place would replace a granted collection's contents without any grant changing; replacing the credential also rebinds whose authority the source's syncs run under, which is a reconnect. The fields are whole-value replacements, as on connect: a repeated proto3 field cannot tell "not sent" from "sent empty", so treating absence as "leave alone" would make clearing a filter impossible and make an accidental omission silently preserve one. Validation is the connect path's own — normalizePaths, normalizeFileExtensions, and validateGitHubAccess against the source's existing credential before the new branch is persisted, because a branch written without being proven readable fails every sync from then on with the source still reading active. A change that moves what is fetched enqueues the forced reconcile "Sync now" already enqueues, in a new reconcileModeReconfigured, and the progress projection reports it as SOURCE_SYNC_TRIGGER_RECONFIGURED. Every decision about forcing now reads forcedReconcileMode rather than comparing to one constant, so a further forcing mode cannot quietly reconcile conditionally. A request that changes nothing writes nothing, records nothing and returns no job id. Co-Authored-By: Claude Fable 5.1 * fix(accounts,frontend): an edit of a connected source refuses what it cannot safely replace Review of #1183 found two ways the edit path reported success for work it had not done, and both came from the same shape: the row was read in one transaction and written in another, with nothing in between to check that what came back still described it. An UPDATE that matched no row returned nil. A source deleted between the read UpdateSource validates against and the statement that writes it therefore committed an audit record, queued a reconcile, and answered the caller with a Datasource and a job id — for a row that was gone. The statement now reports ErrDatasourceSourceNotFound on an empty command tag, which is the answer DeleteDatasourceSource's own RETURNING comment argues for twenty lines below it, and the fake store mirrors it so a test cannot pass on the old swallow. Every field of the request is a whole-field replacement, so every save asserts the source's complete scope — and an unconditional one applied late is indistinguishable from a deliberate revert. A form opened while a source read one way and saved after someone else narrowed it restored the wider scope and re-ingested everything they had just excluded, with nothing in the trail to show it was an accident. The request now carries the scope its author was shown, and a mismatch against the locked row is refused. It carries the scope and not the row's updated_at, because SetDatasourceSourceSynced moves updated_at on every successful sync: a timestamp precondition would refuse a good edit for the sole reason that the source had reconciled since the form opened. What an editor needs guarded is what they were looking at. Both decisions, and the no-op check, are now taken against the row under lock inside the writing transaction, so "it is still there", "it still holds what you saw" and "this changes nothing" are facts about the row being written rather than about a copy of it read earlier. Only the GitHub branch check stays outside, because no transaction should be held open across a provider call. That precondition also corrects the retry message: a second save after a lost response now says the source moved — which it did, by the first call — instead of "nothing changed" while a sync it started was running. Three smaller corrections, all of them claims that were not true: - The progress view substituted the reconfigured note for "nothing was handed off" instead of adding to it, so the one reader who needed to know a sync compiled nothing was told it had re-snapshotted the source. That state is reachable: a forced reconcile lands there when snapshotAt parks an oversized manifest, which is what happens when a narrowed scope is still over the cap — and narrowing is the documented recovery for it. - The edit dialog promised that "everything already ingested" stays. The host does not know that: a snapshot declares what is in scope and emits no removal for a file that left it, so pruning is the consuming module's rule. The dialog now says saving deletes nothing and that what becomes of de-scoped files is up to whatever reads the collection, and the contract it rests on is written down in the accounts AGENTS.md rather than assumed. - The store comment called the read-revision trigger the signal that tells a consumer its snapshot moved. It is the org's authorization revision, which CheckAuthorizationRevision compares; the reconcile is what tells a consumer. It also explains why no migration is owed, which is the question a reader actually has there. Also reverts biome reformatting of nine hunks of the shared datasources gateway test and an import reorder in the stories file, neither of which this change needed. No gate checks frontend formatting, so the churn bought nothing and cost every other branch touching those tests a conflict. Tests: the deleted-row race is interleaved into the window a caller really has (deleting beforehand proves nothing — UpdateSource's own first read answers not-found), asserted at both the locked read and the statement, and the statement's half is covered against a real database. The stale-expectation revert, the lenient comparison that keeps a legacy spelling editable, the missing expectation, and the progress detail each have a test that fails without its fix. Co-Authored-By: Claude Opus 5 (1M context) --------- Co-authored-by: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 --- module/FEATURES.md | 1 + module/WORK_CONTEXTS.md | 6 + .../accounts/saas.events.codefly.yaml | 4 + .../api/accounts/authority/contract.binpb | Bin 1193306 -> 1199552 bytes .../proto/saas/accounts/v1/datasource.proto | 132 ++ .../api/accounts/connect/contract.binpb | Bin 1193306 -> 1199552 bytes .../proto/saas/accounts/v1/datasource.proto | 132 ++ module/contracts/api/catalog.codefly.json | 5 +- module/deployment/generated/asyncapi.json | 25 + module/deployment/generated/communication.md | 8 + .../deployment/generated/event-catalog.json | 100 ++ module/module.package.codefly.yaml | 4 +- module/services/accounts/AGENTS.md | 11 + module/services/accounts/AUTHZ.md | 9 + module/services/accounts/AUTHZ_MATRIX.md | 5 +- .../code/pkg/adapters/datasource_handler.go | 49 + .../pkg/adapters/datasource_sync_progress.go | 7 +- .../code/pkg/business/audit_registry.go | 15 + .../pkg/business/datasource_deliveries.go | 41 +- .../pkg/business/datasource_sync_progress.go | 9 +- .../code/pkg/business/datasource_update.go | 292 ++++ .../pkg/business/datasource_update_test.go | 448 ++++++ .../code/pkg/business/datasources_test.go | 110 ++ .../code/pkg/business/introspection.go | 1 + .../accounts/code/pkg/business/store.go | 11 + .../code/pkg/eventcatalog/catalog_gen.go | 1 + .../accountsv1connect/datasource.connect.go | 39 + .../pkg/gen/saas/accounts/v1/datasource.pb.go | 1235 ++++++++++------- .../saas/accounts/v1/datasource_grpc.pb.go | 48 + .../postgres_datasource_file_filter_test.go | 124 ++ .../pkg/infra/postgres_datasource_sources.go | 113 +- .../generated/api-contract-surfaces.json | 1 + .../accounts/generated/authz-methods.json | 45 + .../accounts/generated/gateway-routes.json | 32 + .../accounts/generated/service-catalog.json | 52 + .../proto/saas/accounts/v1/datasource.proto | 132 ++ .../auth-gateway/code/authz_catalog_gen.go | 1 + .../auth-gateway/code/routing_catalog_gen.go | 2 + .../services/frontend/code/package-lock.json | 8 +- module/services/frontend/code/package.json | 2 +- .../code/packages/codefly-ui/package.json | 2 +- .../generated/contract/catalog.codefly.json | 3 +- .../generated/contract/contract.binpb | Bin 1193306 -> 1199552 bytes .../saas-sdk/generated/library.codefly.yaml | 2 +- .../src/gen/saas/accounts/v1/datasource_pb.ts | 332 +++-- .../code/packages/saas-sdk/package.json | 2 +- .../frontend/code/packages/saas-ui/README.md | 23 +- .../code/packages/saas-ui/package.json | 2 +- .../__tests__/edit-source.test.tsx | 267 ++++ .../datasources/__tests__/gateway.test.tsx | 8 + .../src/datasources/datasources-panel.tsx | 98 +- .../src/datasources/edit-source-form.tsx | 178 +++ .../saas-ui/src/datasources/gateway.ts | 24 + .../saas-ui/src/datasources/queries.ts | 22 + .../saas-ui/src/datasources/schema.ts | 68 +- .../src/datasources/sync-progress-model.ts | 24 +- .../packages/saas-ui/src/datasources/types.ts | 60 +- .../code/packages/saas-ui/src/index.ts | 6 + .../saas-ui/stories/datasources.stories.tsx | 20 + .../src/gen/saas/accounts/v1/datasource_pb.ts | 332 +++-- .../services/frontend/code/ui-inventory.json | 23 + 61 files changed, 4045 insertions(+), 711 deletions(-) create mode 100644 module/services/accounts/code/pkg/business/datasource_update.go create mode 100644 module/services/accounts/code/pkg/business/datasource_update_test.go create mode 100644 module/services/frontend/code/packages/saas-ui/src/datasources/__tests__/edit-source.test.tsx create mode 100644 module/services/frontend/code/packages/saas-ui/src/datasources/edit-source-form.tsx diff --git a/module/FEATURES.md b/module/FEATURES.md index 83841cead..3553311bf 100644 --- a/module/FEATURES.md +++ b/module/FEATURES.md @@ -473,6 +473,7 @@ see `JOBS.md` for the exact boundary and sequencing. | Webhooks (system view) | ✅ | List + delete from admin | | Time-bound impersonation | ✅ | Access tokens are capped at five minutes by default | | Audit log viewer | ✅ | Same UI as user-facing log, super-admin sees all orgs | +| Data sources | ✅ | Connect a repository, edit what a connected source fetches in place (branch, paths, file types — `UpdateSource`, #1181), reconcile, reconnect, remove; collection read grants beside it | ### Compliance & legal diff --git a/module/WORK_CONTEXTS.md b/module/WORK_CONTEXTS.md index eb9023d24..017917f45 100644 --- a/module/WORK_CONTEXTS.md +++ b/module/WORK_CONTEXTS.md @@ -573,6 +573,12 @@ previous row (`replaced`) and records a new one under the reconnecting person, atomically. Sources connected before a module declared a binding carry no delegation; nothing backfills one, so a person must reconnect them. +`UpdateSource` is not one of those acts and records no delegation. It changes +what a source fetches, never its credential, so the authority its syncs already +run under is exactly the authority they keep — and recording a fresh row for the +editor would move a source's sync authority onto whoever last narrowed a path +filter. + **A connect that can record nothing is REFUSED, not reported as done.** Recording no delegation is correct for a deployment where no composed module accepts one, and that is the whole answer there. It is a lie when the module diff --git a/module/contracts/accounts/saas.events.codefly.yaml b/module/contracts/accounts/saas.events.codefly.yaml index 112606a89..081da4b2e 100644 --- a/module/contracts/accounts/saas.events.codefly.yaml +++ b/module/contracts/accounts/saas.events.codefly.yaml @@ -239,6 +239,10 @@ publishes: schema: saas/events/v1/events.proto#EventEnvelope visibility: external retention: 30d + - type: saas.datasource.source.updated + schema: saas/events/v1/events.proto#EventEnvelope + visibility: external + retention: 30d - type: saas.datasource.sync.completed schema: saas/events/v1/events.proto#EventEnvelope visibility: external diff --git a/module/contracts/api/accounts/authority/contract.binpb b/module/contracts/api/accounts/authority/contract.binpb index 0364c84f1a17569895b4952ffda03ea25163ca2a..590c0a8eb1d2d00c9520361083a0d0e6c244e78f 100644 GIT binary patch delta 40376 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 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: **269 RPCs** across **36 services**. Missing, invalid, or unclassified procedures are denied by both Connect and gRPC interceptors. +Inventory: **270 RPCs** across **36 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. @@ -76,6 +76,7 @@ The compact tier is retained for compatibility. Guards, resource bindings, audit | `/saas.accounts.v1.DatasourceService/RevokeSourceDelegation` | unary | `—` | `org_admin` | exposure=AUTHENTICATED; tenant=ORG_ADMIN | — | org_id → ORGANIZATION/DIRECT_ID | SUCCESS: saas.datasource.delegation.revoked | FORBIDDEN / STANDARD_WRITE | INTERNAL → CONFIDENTIAL | Revoke one of the organization's source delegations. | | `/saas.accounts.v1.DatasourceService/SyncSource` | unary | `—` | `org_admin` | exposure=AUTHENTICATED; tenant=ORG_ADMIN | — | org_id → ORGANIZATION/DIRECT_ID | SUCCESS: saas.datasource.source.synced | FORBIDDEN / STANDARD_WRITE | SECRET → INTERNAL | Pull the source's current contents and enqueue ingestion deliveries. | | `/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.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. | @@ -283,7 +284,7 @@ The compact tier is retained for compatibility. Guards, resource bindings, audit - `auth`: 43 - `internal`: 54 - `mfa`: 5 -- `org_admin`: 64 +- `org_admin`: 65 - `org_member`: 56 - `platform_admin`: 29 - `public`: 18 diff --git a/module/services/accounts/code/pkg/adapters/datasource_handler.go b/module/services/accounts/code/pkg/adapters/datasource_handler.go index 9ffbc1822..f37add227 100644 --- a/module/services/accounts/code/pkg/adapters/datasource_handler.go +++ b/module/services/accounts/code/pkg/adapters/datasource_handler.go @@ -116,6 +116,55 @@ func (h *datasourceConnectHandler) AddSource( return connect.NewResponse(&gen.AddSourceResponse{Datasource: datasourceSourceToProto(source, h.svc.DatasourceConnectors(), h.svc.LiveDeliveryFor(source))}), nil } +func (h *datasourceConnectHandler) UpdateSource( + ctx context.Context, + req *connect.Request[gen.UpdateSourceRequest], +) (*connect.Response[gen.UpdateSourceResponse], error) { + ctx = connectCtx(ctx, req.Header()) + actorID, err := callerID(ctx) + if err != nil { + return nil, err + } + if err := requireOrgAdmin(ctx, actorID, req.Msg.OrgId); err != nil { + return nil, translateGRPCError(err) + } + input := business.UpdateSourceInput{ + OrgID: req.Msg.OrgId, + ID: req.Msg.Id, + Branch: req.Msg.Branch, + Paths: req.Msg.Paths, + FileExtensions: req.Msg.FileExtensions, + } + // The scope the caller was shown. The descriptor marks it required, so an + // absent one is already refused before this handler runs; it is mapped + // rather than defaulted so a request that somehow arrives without one is + // refused by the business rule too, never applied unconditionally. + if expected := req.Msg.GetExpected(); expected != nil { + input.Expected = &business.DatasourceFetchScope{ + Branch: expected.GetBranch(), + Paths: expected.GetPaths(), + FileExtensions: expected.GetFileExtensions(), + } + } + source, jobID, err := h.svc.UpdateSource(ctx, actorID, input) + if err != nil { + if errors.Is(err, business.ErrDatasourceSourceNotFound) { + return nil, connect.NewError(connect.CodeNotFound, err) + } + // The source moved under the editor. FailedPrecondition and not Aborted: + // retrying the same request would be refused again for the same reason, + // and what resolves it is reopening the form on the current scope. + if errors.Is(err, business.ErrDatasourceSourceScopeChanged) { + return nil, connect.NewError(connect.CodeFailedPrecondition, err) + } + return nil, translateGRPCError(err) + } + return connect.NewResponse(&gen.UpdateSourceResponse{ + Datasource: datasourceSourceToProto(source, h.svc.DatasourceConnectors(), h.svc.LiveDeliveryFor(source)), + JobId: jobID, + }), nil +} + func (h *datasourceConnectHandler) GetDatasourceCatalog( ctx context.Context, req *connect.Request[gen.GetDatasourceCatalogRequest], diff --git a/module/services/accounts/code/pkg/adapters/datasource_sync_progress.go b/module/services/accounts/code/pkg/adapters/datasource_sync_progress.go index 0e75e2f59..8d430cac4 100644 --- a/module/services/accounts/code/pkg/adapters/datasource_sync_progress.go +++ b/module/services/accounts/code/pkg/adapters/datasource_sync_progress.go @@ -19,9 +19,10 @@ var sourceSyncPhases = map[business.DatasourceSyncPhase]gen.SourceSyncPhase{ } var sourceSyncTriggers = map[business.DatasourceSyncTrigger]gen.SourceSyncTrigger{ - business.DatasourceSyncTriggerManual: gen.SourceSyncTrigger_SOURCE_SYNC_TRIGGER_MANUAL, - business.DatasourceSyncTriggerScheduled: gen.SourceSyncTrigger_SOURCE_SYNC_TRIGGER_SCHEDULED, - business.DatasourceSyncTriggerWebhook: gen.SourceSyncTrigger_SOURCE_SYNC_TRIGGER_WEBHOOK, + business.DatasourceSyncTriggerManual: gen.SourceSyncTrigger_SOURCE_SYNC_TRIGGER_MANUAL, + business.DatasourceSyncTriggerScheduled: gen.SourceSyncTrigger_SOURCE_SYNC_TRIGGER_SCHEDULED, + business.DatasourceSyncTriggerWebhook: gen.SourceSyncTrigger_SOURCE_SYNC_TRIGGER_WEBHOOK, + business.DatasourceSyncTriggerReconfigured: gen.SourceSyncTrigger_SOURCE_SYNC_TRIGGER_RECONFIGURED, } var sourceSyncFailureReasons = map[business.DatasourceSyncFailureReason]gen.SourceSyncFailureReason{ diff --git a/module/services/accounts/code/pkg/business/audit_registry.go b/module/services/accounts/code/pkg/business/audit_registry.go index 9e8ba21f9..97be734c4 100644 --- a/module/services/accounts/code/pkg/business/audit_registry.go +++ b/module/services/accounts/code/pkg/business/audit_registry.go @@ -473,6 +473,7 @@ const ( EventDatasourceSyncFailed EventType = "saas.datasource.sync.failed" EventDatasourceSourceSynced EventType = "saas.datasource.source.synced" EventDatasourceSourceRemoved EventType = "saas.datasource.source.removed" + EventDatasourceSourceUpdated EventType = "saas.datasource.source.updated" EventDatasourceChangeSetCompiled EventType = "saas.datasource.change_set_compiled" EventDatasourceForcePushReconciled EventType = "saas.datasource.force_push_reconciled" EventDatasourceBranchDeleted EventType = "saas.datasource.branch_deleted" @@ -833,6 +834,20 @@ var auditEventCatalog = []AuditEventDefinition{ revised(mutation(EventDatasourceSourceRemoved, CategorySystem, "A datasource was removed.", enum("provider", DatasourceProviderGitHub, DatasourceProviderAPI, DatasourceProviderCrawler, DatasourceProviderUpload), str("repo"), str("boundary")), 2), + // What a source fetches is what a consumer's collection holds, so the trail + // records both sides of the move: the branch it was on and the branch it is + // on now, with the filters as they stand. `previous_branch` is empty when + // the branch did not change, which is how the trail distinguishes a + // re-aimed source from one whose filters were narrowed in place. + // + // `boundary` is the collection the source feeds, spelled as on the document + // and source-removed events so one payload filter reads all three — and so + // the collection grant covers this event, which isCollectionBoundaryEvent + // reads this declaration for. An edit never moves the boundary, but it is + // the collection whose contents the next snapshot replaces, so whoever may + // read that collection is exactly who needs to see it. + mutation(EventDatasourceSourceUpdated, CategorySystem, "A connected datasource's branch or path filters were changed.", + str("repo"), str("branch"), str("previous_branch"), strs("paths"), strs("file_extensions"), str("boundary")), observation(EventDatasourceSyncCompleted, CategorySystem, "A datasource ingestion job completed.", sourceSyncFields...), observation(EventDatasourceSyncFailed, CategorySystem, "A datasource ingestion attempt failed and may retry.", sourceSyncFields...), observation(EventDatasourceChangeSetCompiled, CategorySystem, "A GitHub delivery was compiled into a change set.", diff --git a/module/services/accounts/code/pkg/business/datasource_deliveries.go b/module/services/accounts/code/pkg/business/datasource_deliveries.go index 0c203fb4a..9145e80a5 100644 --- a/module/services/accounts/code/pkg/business/datasource_deliveries.go +++ b/module/services/accounts/code/pkg/business/datasource_deliveries.go @@ -70,6 +70,13 @@ const ( reconcileModeConditional = "conditional" reconcileModeForce = "force" + // reconcileModeReconfigured is a forced reconcile an UpdateSource asked for: + // the source's branch or filters moved, so the snapshot it takes covers a + // different set of files than the last one did. It forces exactly as + // reconcileModeForce does — which is why every decision about forcing reads + // forcedReconcileMode rather than comparing to one constant — and is a + // separate value only so the sync it drives can say why it ran. + reconcileModeReconfigured = "reconfigured" changeTypeModified = "modified" changeTypeRemoved = "removed" @@ -260,9 +267,12 @@ func (s *Service) NewDatasourceDeliveryJobHandler() jobs.Handler { resultErr = s.parkUnreadablePublicSource(ctx, source, resultErr) resultErr = datasourceProcessingError(resultErr) trigger := "reconcile" - if envelope.GetTopic() == datasourcePushTopic { + switch { + case envelope.GetTopic() == datasourcePushTopic: trigger = "webhook" - } else if envelope.GetAttributes()[attrReconcileMode] == reconcileModeForce { + case envelope.GetAttributes()[attrReconcileMode] == reconcileModeReconfigured: + trigger = "reconfigured" + case envelope.GetAttributes()[attrReconcileMode] == reconcileModeForce: trigger = "manual" } fields := datasourceFailureFields(resultErr, source.Repo, trigger) @@ -283,7 +293,7 @@ func (s *Service) NewDatasourceDeliveryJobHandler() jobs.Handler { // A reconcile a person asked for ("Sync now", the first sync at connect) // is interactive: it may spend the whole credential budget. Webhook // deliveries and the periodic reconcile are background and yield. - if envelope.GetAttributes()[attrReconcileMode] == reconcileModeForce { + if forcedReconcileMode(envelope.GetAttributes()[attrReconcileMode]) { ctx = connector.WithPriority(ctx, connector.PriorityInteractive) } @@ -301,7 +311,7 @@ func (s *Service) NewDatasourceDeliveryJobHandler() jobs.Handler { } return err case DatasourceReconcileTopic: - force := envelope.GetAttributes()[attrReconcileMode] == reconcileModeForce + force := forcedReconcileMode(envelope.GetAttributes()[attrReconcileMode]) _, err := s.ReconcileGitHubSource(ctx, source, force, envelope.GetId()) return err default: @@ -949,7 +959,8 @@ func (s *Service) RunDatasourceReconcile(ctx context.Context) (int, error) { // STOPGAP (issue #1112). func (s *Service) enqueueScheduledReconcile(ctx context.Context, source *DatasourceSource) error { if source.Provider == DatasourceProviderGitHub { - return s.enqueueReconcile(ctx, source, reconcileModeConditional, scheduledReconcileKey(source)) + _, err := s.enqueueReconcile(ctx, source, reconcileModeConditional, scheduledReconcileKey(source)) + return err } return s.enqueueScheduledPullSync(ctx, source) } @@ -977,8 +988,11 @@ func scheduledReconcileKey(source *DatasourceSource) string { return "datasource.scheduled:" + source.ID + ":" + source.NextReconcileAt.UTC().Format(time.RFC3339Nano) } -func (s *Service) enqueueReconcile(ctx context.Context, source *DatasourceSource, mode, idempotencyKey string) error { - _, err := s.datasourceJobs.EnqueueJob(ctx, &jobsv1.EnqueueJobRequest{ +// enqueueReconcile queues one reconcile of a source in the given mode and +// returns the durable job id, which is what a caller hands back as the sync to +// watch. Callers with nothing to watch discard it. +func (s *Service) enqueueReconcile(ctx context.Context, source *DatasourceSource, mode, idempotencyKey string) (string, error) { + response, err := s.datasourceJobs.EnqueueJob(ctx, &jobsv1.EnqueueJobRequest{ Job: &jobsv1.NewJob{ Direction: jobsv1.JobDirection_JOB_DIRECTION_INBOX, Scope: &jobsv1.JobScope{Value: &jobsv1.JobScope_Global{Global: true}}, @@ -999,7 +1013,18 @@ func (s *Service) enqueueReconcile(ctx context.Context, source *DatasourceSource }, }, }) - return err + if err != nil { + return "", err + } + return response.GetJobId(), nil +} + +// forcedReconcileMode reports whether a reconcile mode takes a full snapshot +// regardless of the source's cursor. Every mode that forces must be named here: +// a new forcing mode compared against one constant somewhere else would quietly +// reconcile conditionally, find the head unmoved, and hand nothing off. +func forcedReconcileMode(mode string) bool { + return mode == reconcileModeForce || mode == reconcileModeReconfigured } // enqueueFirstPullSync is a pull source's sync at connect. It is the scheduled diff --git a/module/services/accounts/code/pkg/business/datasource_sync_progress.go b/module/services/accounts/code/pkg/business/datasource_sync_progress.go index 60ed3d66a..730c08bc4 100644 --- a/module/services/accounts/code/pkg/business/datasource_sync_progress.go +++ b/module/services/accounts/code/pkg/business/datasource_sync_progress.go @@ -77,6 +77,11 @@ const ( DatasourceSyncTriggerManual DatasourceSyncTriggerScheduled DatasourceSyncTriggerWebhook + // DatasourceSyncTriggerReconfigured is a sync an UpdateSource asked for: the + // source's branch or filters moved, so this pass re-snapshots it at the new + // scope. A reader needs it to tell a change set that is large because the + // source was re-aimed from one that is large because the repository moved. + DatasourceSyncTriggerReconfigured ) // DatasourceSyncFailureReason classifies why a sync waits or failed. @@ -238,6 +243,8 @@ func datasourceSyncTrigger(record DatasourceSyncRecord) DatasourceSyncTrigger { switch { case record.Topic == datasourcePushTopic: return DatasourceSyncTriggerWebhook + case record.ReconcileMode == reconcileModeReconfigured: + return DatasourceSyncTriggerReconfigured case record.ReconcileMode == reconcileModeForce: return DatasourceSyncTriggerManual case record.Topic == DatasourceReconcileTopic: @@ -316,7 +323,7 @@ func (s *Service) startFirstSync(ctx context.Context, source *DatasourceSource) } var err error if source.Provider == DatasourceProviderGitHub { - err = s.enqueueReconcile(ctx, source, reconcileModeForce, NewIDString()) + _, err = s.enqueueReconcile(ctx, source, reconcileModeForce, NewIDString()) } else { err = s.enqueueFirstPullSync(ctx, source) } diff --git a/module/services/accounts/code/pkg/business/datasource_update.go b/module/services/accounts/code/pkg/business/datasource_update.go new file mode 100644 index 000000000..05675e6c2 --- /dev/null +++ b/module/services/accounts/code/pkg/business/datasource_update.go @@ -0,0 +1,292 @@ +package business + +import ( + "context" + "errors" + "slices" + "strings" + "time" + + "accounts/pkg/jobs" + + "github.com/codefly-dev/core/wool" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// ErrDatasourceSourceScopeChanged reports that a source's fetch scope moved +// after the caller read it, so the whole-field replacement they asked for was +// refused instead of applied over the newer scope. +var ErrDatasourceSourceScopeChanged = errors.New("This source's branch or filters changed after this form was opened, so saving would have overwritten that change. Close it and edit the source again to work from its current scope.") + +// DatasourceFetchScope is what a source fetches: the three values an edit +// replaces, together, so one can be stated as a whole. +type DatasourceFetchScope struct { + Branch string + Paths []string + FileExtensions []string +} + +// UpdateSourceInput is what a connected source may be re-aimed at: its branch +// and its path and file-type filters. Every field is the whole value the source +// is left with, never a patch — see UpdateSource. +type UpdateSourceInput struct { + OrgID string + ID string + Branch string + Paths []string + FileExtensions []string + // Expected is the scope the caller was shown, which the edit is accepted + // against. Required, and nil is refused rather than read as "apply it + // anyway": a whole-field replacement applied unconditionally is precisely + // how one editor's save erases another's. + Expected *DatasourceFetchScope +} + +// sameFetchScope reports whether a scope is the one a source already holds. +// +// Both sides are normalized the same way before comparing, which is what makes +// re-submitting the values a form was filled with a no-op rather than a change: +// the stored row may hold a spelling an earlier normalizer let through (an +// untrimmed prefix, an uppercase suffix), and comparing raw would report that +// as a move and re-send the whole repository to the consuming module. The +// comparison is deliberately more lenient than normalizeFileExtensions, which +// refuses what it cannot store — a value already in the row has to stay +// comparable even if it would not be accepted today, or the source it belongs +// to could never be edited at all. +func sameFetchScope(scope DatasourceFetchScope, source *DatasourceSource) bool { + return strings.TrimSpace(scope.Branch) == strings.TrimSpace(source.Branch) && + slices.Equal(normalizePaths(scope.Paths), normalizePaths(source.Paths)) && + slices.Equal(comparableFileExtensions(scope.FileExtensions), comparableFileExtensions(source.FileExtensions)) +} + +// comparableFileExtensions lowercases, trims and de-duplicates a file-type +// filter for comparison only, in the order normalizeFileExtensions would have +// produced, and refuses nothing. +func comparableFileExtensions(values []string) []string { + var out []string + seen := make(map[string]struct{}, len(values)) + for _, raw := range values { + value := strings.ToLower(strings.TrimSpace(raw)) + if value == "" { + continue + } + if _, ok := seen[value]; ok { + continue + } + seen[value] = struct{}{} + out = append(out, value) + } + return out +} + +// UpdateSource changes what an already-connected source fetches, keeping its +// identity: the same row, the same credential envelope, the same sync history, +// and the same collection its Entries land in. That is the whole point of it +// existing — the only way to move a source's branch or paths used to be delete +// and re-add, which drops the sync cursor and hands the consuming module a +// collection that starts again from nothing. +// +// What it will not do is as deliberate as what it does. The repository, the +// provider, the boundary node and the credential are each the identity of +// something a consumer already holds, so none of them is mutable here: a source +// pointed at another repository is another source, and re-aiming one in place +// would silently replace a collection's contents with a different repository's. +// The credential has its own path (a reconnect through SyncSource), because +// replacing it also moves whose authority the source's syncs run under. +// +// The fields are applied as whole values, exactly as on connect: empty paths +// ingest the whole repository and empty file extensions admit every file type. +// A repeated proto3 field cannot distinguish "not sent" from "sent empty", so +// treating absence as "leave alone" would make clearing a filter impossible and +// make an accidental omission silently preserve one; the contract says so, and +// the caller repeats what it means to keep. +// +// That is also why the request carries the scope it expects to be replacing. +// Stating a whole scope means every save asserts the complete state, so an +// unconditional one applied late is indistinguishable from a deliberate revert: +// a form opened while the source read one way and saved after someone else +// narrowed it would restore the wider scope and re-ingest everything the other +// person had just excluded, with nothing in the trail to show it was an +// accident. Input.Expected is checked against the locked row, and a mismatch is +// refused as ErrDatasourceSourceScopeChanged. +// +// Validation and normalization are the connect path's own — validRepo is not +// re-run because the repository is not touched, but the branch is proven +// readable with the source's existing credential before anything is written, +// and the paths and extensions go through normalizePaths and +// normalizeFileExtensions, so a source can never hold a scope that connecting +// would have refused. +// +// A change that moves what is fetched enqueues a forced reconcile in +// reconcileModeReconfigured and returns its job id: the next snapshot covers a +// different set of files, which for a consumer is a new snapshot of the +// collection, and the sync's progress reports DatasourceSyncTriggerReconfigured +// so a reader can tell that from an ordinary re-sync. A request that changes +// nothing writes nothing, records nothing and returns an empty job id. +// +// Every decision that governs the write is taken against the row under lock, +// inside the writing transaction: whether it is still there, whether it still +// holds the scope the caller was shown, and whether the request moves it. Only +// the GitHub branch check happens outside, because no transaction should be +// held open across a provider call — and it is sound there because the +// repository and the credential it validates against are not editable, and the +// expectation proved under lock is what decides whether the branch moved at all. +func (s *Service) UpdateSource(ctx context.Context, actorID string, input UpdateSourceInput) (*DatasourceSource, string, error) { + w := wool.Get(ctx).In("UpdateSource") + orgID := strings.TrimSpace(input.OrgID) + id := strings.TrimSpace(input.ID) + if orgID == "" || id == "" { + return nil, "", w.NewError("org id and source id are required") + } + if input.Expected == nil { + return nil, "", status.Error(codes.InvalidArgument, "the scope this edit expects to replace is required") + } + source, err := s.GetDatasourceSource(ctx, orgID, id) + if err != nil { + return nil, "", err + } + // Branch and path filters are the GitHub connector's config. The other + // providers are configured by their own fields (a base URL, a sitemap, a + // bucket prefix), and silently accepting a branch for one of them would + // report a change that changed nothing. + if source.Provider != DatasourceProviderGitHub { + return nil, "", status.Error(codes.FailedPrecondition, "only a github source has a branch and path filters") + } + + branch := strings.TrimSpace(input.Branch) + paths := normalizePaths(input.Paths) + extensions, err := normalizeFileExtensions(input.FileExtensions) + if err != nil { + return nil, "", w.Wrap(err) + } + + // The branch is proven readable before the transaction opens, and only when + // the request moves it away from the scope the caller was shown. That + // expectation is verified under lock below, so a branch that is about to be + // written has always been validated first. + expected := *input.Expected + if branch != strings.TrimSpace(expected.Branch) { + if err := s.validateGitHubBranch(ctx, source, branch); err != nil { + return nil, "", err + } + } + + var ( + updated DatasourceSource + changed bool + ) + if err := s.store.WithOrgTx(ctx, orgID, func(ctx context.Context) error { + current, err := s.store.LockDatasourceSourceFetchScope(ctx, orgID, id) + if err != nil { + return err + } + // Deleted since the read above. Refusing here is what keeps an audit + // record and a queued sync from describing a source that is gone. + if current == nil { + return ErrDatasourceSourceNotFound + } + // The scope moved under the caller. Their request states a whole scope, + // so applying it now would overwrite whatever replaced what they saw — + // most often restoring a filter someone else had just narrowed, and + // re-ingesting everything it excluded. + if !sameFetchScope(expected, current) { + return ErrDatasourceSourceScopeChanged + } + // The boundary label is not in the locked projection (it comes from a + // join with no row to lock), so it is carried over from the read above; + // an edit cannot move the boundary, so it cannot have gone stale. + updated = *current + updated.BoundaryLabel = source.BoundaryLabel + // Decided against the locked row, so "nothing changed" is a fact about + // the stored scope and not about a copy of it read earlier. + if sameFetchScope(DatasourceFetchScope{Branch: branch, Paths: paths, FileExtensions: extensions}, current) { + return nil + } + previousBranch := current.Branch + updated.Branch = branch + updated.Paths = paths + updated.FileExtensions = extensions + if err := s.store.UpdateDatasourceSourceFetchScope(ctx, &updated); err != nil { + return err + } + changed = true + payload := map[string]any{ + "repo": updated.Repo, + "branch": branch, + "paths": paths, + "file_extensions": extensions, + // The collection whose contents the re-snapshot replaces, so the + // record reaches whoever holds a grant on it. + "boundary": updated.BoundaryNodeID, + } + // Empty unless the branch moved, which is how the trail tells a re-aimed + // source from one whose filters were narrowed where it stands. + if branch != previousBranch { + payload["previous_branch"] = previousBranch + } + return s.emitTx(ctx, actorID, "user", EventDatasourceSourceUpdated, "datasource", updated.ID, orgID, payload) + }); err != nil { + // Both refusals are the caller's answer, not a persistence fault, so + // they reach the handler as themselves rather than wrapped in one. + if errors.Is(err, ErrDatasourceSourceNotFound) || errors.Is(err, ErrDatasourceSourceScopeChanged) { + return nil, "", err + } + return nil, "", w.Wrapf(err, "persist datasource source update") + } + if !changed { + return &updated, "", nil + } + + jobID, err := s.enqueueReconfiguredSync(ctx, &updated) + if err != nil { + // The source is already committed at its new scope. The periodic + // reconcile and "Sync now" both still re-snapshot it, so the update + // stands and the caller is told there is no sync to watch rather than + // being told the edit failed and retrying it. + w.Warn("enqueue reconfigured sync failed; the periodic reconcile will re-snapshot the source", + wool.Field("source", updated.ID), wool.ErrField(err)) + return &updated, "", nil + } + return &updated, jobID, nil +} + +// enqueueReconfiguredSync queues the re-snapshot an update owes the consuming +// module: the same forced reconcile "Sync now" and the first sync at connect +// enqueue, in the mode that names why it ran. +func (s *Service) enqueueReconfiguredSync(ctx context.Context, source *DatasourceSource) (string, error) { + if s.datasourceJobs == nil { + return "", errors.New("datasource connector is not configured") + } + return s.enqueueReconcile(ctx, source, reconcileModeReconfigured, NewIDString()) +} + +// validateGitHubBranch proves the source's own credential can read a branch +// before that branch is persisted, through the same validateGitHubAccess the +// connect path and the sync preflight use. +// +// It refuses where checkGitHubSyncPreflight defers. The preflight may hand a +// transient GitHub failure to the durable worker because the work it guards +// retries; there is nothing to retry here, and a branch that was never proven +// readable would be written to the row and then fail every sync from now on, +// with the source still reading active. +func (s *Service) validateGitHubBranch(ctx context.Context, source *DatasourceSource, branch string) error { + if s.datasourceCipher == nil { + return status.Error(codes.FailedPrecondition, "datasource secret cipher is not configured") + } + ctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + client, err := s.githubClientForSource(ctx, source) + if err != nil { + var failure *jobs.ProcessingError + if errors.As(err, &failure) { + code := codes.FailedPrecondition + if failure.Retryable { + code = codes.Unavailable + } + return status.Error(code, failure.Failure.Message) + } + return err + } + return validateGitHubAccess(ctx, client, source.Repo, branch) +} diff --git a/module/services/accounts/code/pkg/business/datasource_update_test.go b/module/services/accounts/code/pkg/business/datasource_update_test.go new file mode 100644 index 000000000..33f3a2af5 --- /dev/null +++ b/module/services/accounts/code/pkg/business/datasource_update_test.go @@ -0,0 +1,448 @@ +package business_test + +import ( + "context" + "errors" + "reflect" + "testing" + + "accounts/pkg/business" + "accounts/pkg/datasource/github" + jobsv1 "accounts/pkg/gen/saas/jobs/v1" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// editableSource is a connected GitHub source with a scope worth narrowing, and +// the first sync the connect enqueued forgotten so each test counts only the +// jobs its own edit produced. +func editableSource(t *testing.T, svc *business.Service) *business.DatasourceSource { + t.Helper() + return addSource(t, svc, business.AddGitHubSourceInput{ + OrgID: testOrg, + Repo: "acme/handbook", + Paths: []string{"docs"}, + FileExtensions: []string{".md"}, + Branch: "main", + CollectionLabel: "handbook", + AccessToken: "ghp_secret", + }) +} + +// scopeOf is the expectation an edit carries: the scope its author was shown. +// Every UpdateSource below states one, because the host refuses an edit that +// does not — a whole-field replacement applied unconditionally is how one +// editor's save erases another's. +func scopeOf(source *business.DatasourceSource) *business.DatasourceFetchScope { + return &business.DatasourceFetchScope{ + Branch: source.Branch, + Paths: source.Paths, + FileExtensions: source.FileExtensions, + } +} + +// The whole point of UpdateSource: the branch and filters move on the SAME row, +// so the credential envelope, the collection and the repository are exactly +// where they were. Editing used to mean delete plus re-add, which drops all +// three along with the sync cursor. +func TestUpdateSource_MovesTheScopeAndKeepsTheSourcesIdentity(t *testing.T) { + store := newDatasourceFakeStore() + producer := &recordingProducer{} + svc, audit := newDatasourceService(store, producer, nil) + source := editableSource(t, svc) + + updated, jobID, err := svc.UpdateSource(context.Background(), "actor-1", business.UpdateSourceInput{ + OrgID: testOrg, + ID: source.ID, + Branch: "release", + // Duplicates and slashes are normalized by the connect path's own + // normalizePaths, so an edit cannot store a scope a connect would refuse. + Paths: []string{"/docs/", "docs", "guides"}, + FileExtensions: []string{".MD", ".mdx"}, + Expected: scopeOf(source), + }) + if err != nil { + t.Fatal(err) + } + if updated.ID != source.ID || updated.Repo != source.Repo || + updated.BoundaryNodeID != source.BoundaryNodeID || + updated.CredentialSecretRef != source.CredentialSecretRef { + t.Fatalf("identity moved: %+v", updated) + } + if updated.Branch != "release" { + t.Fatalf("branch = %q, want release", updated.Branch) + } + if want := []string{"docs", "guides"}; !reflect.DeepEqual(updated.Paths, want) { + t.Fatalf("paths = %v, want %v", updated.Paths, want) + } + if want := []string{".md", ".mdx"}; !reflect.DeepEqual(updated.FileExtensions, want) { + t.Fatalf("file extensions = %v, want %v", updated.FileExtensions, want) + } + + // The read-back proves the row was written, not just the struct returned. + stored, err := svc.GetDatasourceSource(context.Background(), testOrg, source.ID) + if err != nil { + t.Fatal(err) + } + if stored.Branch != "release" || !reflect.DeepEqual(stored.FileExtensions, []string{".md", ".mdx"}) { + t.Fatalf("stored source = %+v", stored) + } + + // The move owes the consuming module a re-snapshot, named as one. + if jobID == "" { + t.Fatal("no sync was enqueued for a change that moved what is fetched") + } + jobs := producer.reconcileJobsFor(source.ID) + if len(jobs) != 1 { + t.Fatalf("reconcile jobs = %d, want 1", len(jobs)) + } + if mode := jobs[0].GetAttributes()["datasource.reconcile_mode"]; mode != "reconfigured" { + t.Fatalf("reconcile mode = %q, want reconfigured", mode) + } + + entries := audit.entriesOf(business.EventDatasourceSourceUpdated) + if len(entries) != 1 { + t.Fatalf("updated events = %d, want 1", len(entries)) + } + want := map[string]any{ + "repo": "acme/handbook", + "branch": "release", + "previous_branch": "main", + "paths": []string{"docs", "guides"}, + "file_extensions": []string{".md", ".mdx"}, + // The collection the re-snapshot replaces, so a collection grant reaches + // this record (isCollectionBoundaryEvent reads the declaration). + "boundary": source.BoundaryNodeID, + } + if !reflect.DeepEqual(entries[0].Payload, want) { + t.Fatalf("payload = %v, want %v", entries[0].Payload, want) + } + requireDeclaredPayloads(t, audit) +} + +// A sync re-sends the whole source to the consuming module, so submitting the +// values the source already holds must not start one. Normalization happens +// first: the form's "/docs/" is the stored "docs", and comparing before +// normalizing would call that a change. +func TestUpdateSource_UnchangedScopeWritesNothing(t *testing.T) { + producer := &recordingProducer{} + svc, audit := newDatasourceService(newDatasourceFakeStore(), producer, nil) + source := editableSource(t, svc) + + _, jobID, err := svc.UpdateSource(context.Background(), "actor-1", business.UpdateSourceInput{ + OrgID: testOrg, + ID: source.ID, + Branch: " main ", + Paths: []string{"/docs/"}, + FileExtensions: []string{".md"}, + Expected: scopeOf(source), + }) + if err != nil { + t.Fatal(err) + } + if jobID != "" { + t.Fatalf("job id = %q, want empty: nothing changed", jobID) + } + if jobs := producer.reconcileJobsFor(source.ID); len(jobs) != 0 { + t.Fatalf("reconcile jobs = %d, want 0", len(jobs)) + } + if entries := audit.entriesOf(business.EventDatasourceSourceUpdated); len(entries) != 0 { + t.Fatalf("updated events = %d, want 0: nothing changed", len(entries)) + } +} + +// A branch the source's credential cannot read must be refused BEFORE the row +// is written. Persisted, it would leave the source reading active while every +// sync from then on failed. +func TestUpdateSource_RefusesAnUnreadableBranchWithoutWriting(t *testing.T) { + gh := &fakeGitHub{defaultBranch: "main", commit: "abc"} + producer := &recordingProducer{} + svc, audit := newDatasourceService(newDatasourceFakeStore(), producer, gh) + source := editableSource(t, svc) + + gh.resolveErr = github.ErrNotFound + _, _, err := svc.UpdateSource(context.Background(), "actor-1", business.UpdateSourceInput{ + OrgID: testOrg, ID: source.ID, Branch: "gone", Paths: source.Paths, FileExtensions: source.FileExtensions, + Expected: scopeOf(source), + }) + if status.Code(err) != codes.FailedPrecondition { + t.Fatalf("error = %v, want FailedPrecondition", err) + } + gh.resolveErr = nil + + stored, err := svc.GetDatasourceSource(context.Background(), testOrg, source.ID) + if err != nil { + t.Fatal(err) + } + if stored.Branch != "main" { + t.Fatalf("stored branch = %q, want main: the refusal must not have written", stored.Branch) + } + if entries := audit.entriesOf(business.EventDatasourceSourceUpdated); len(entries) != 0 { + t.Fatalf("updated events = %d, want 0", len(entries)) + } + if jobs := producer.reconcileJobsFor(source.ID); len(jobs) != 0 { + t.Fatalf("reconcile jobs = %d, want 0", len(jobs)) + } +} + +// Narrowing the filters alone never asks GitHub anything: the branch did not +// move, so there is nothing new to prove readable, and a GitHub outage must not +// block an edit that cannot depend on it. +func TestUpdateSource_FilterOnlyEditNeedsNoBranchValidation(t *testing.T) { + gh := &fakeGitHub{defaultBranch: "main", commit: "abc"} + producer := &recordingProducer{} + svc, audit := newDatasourceService(newDatasourceFakeStore(), producer, gh) + source := addSource(t, svc, business.AddGitHubSourceInput{ + OrgID: testOrg, Repo: "acme/handbook", Paths: []string{"docs"}, + Branch: "main", CollectionLabel: "handbook", AccessToken: "ghp_secret", + }) + // Only after the connect: GitHub answers nothing from here on, and the edit + // below must still succeed because it asks GitHub nothing. + gh.resolveErr = github.ErrNotFound + + updated, jobID, err := svc.UpdateSource(context.Background(), "actor-1", business.UpdateSourceInput{ + OrgID: testOrg, ID: source.ID, Branch: "main", Paths: []string{"docs", "guides"}, + Expected: scopeOf(source), + }) + if err != nil { + t.Fatal(err) + } + if jobID == "" || len(updated.Paths) != 2 { + t.Fatalf("job id = %q, paths = %v", jobID, updated.Paths) + } + // The branch stayed, so the trail records no previous one to compare against. + entries := audit.entriesOf(business.EventDatasourceSourceUpdated) + if len(entries) != 1 { + t.Fatalf("updated events = %d, want 1", len(entries)) + } + if _, ok := entries[0].Payload["previous_branch"]; ok { + t.Fatalf("payload names a previous branch though the branch did not move: %v", entries[0].Payload) + } + requireDeclaredPayloads(t, audit) +} + +func TestUpdateSource_Validation(t *testing.T) { + svc, _ := newDatasourceService(newDatasourceFakeStore(), &recordingProducer{}, nil) + source := editableSource(t, svc) + + for name, tc := range map[string]struct { + input business.UpdateSourceInput + code codes.Code + }{ + "no source id": { + input: business.UpdateSourceInput{OrgID: testOrg, Expected: scopeOf(source)}, + }, + "unknown source": { + input: business.UpdateSourceInput{OrgID: testOrg, ID: business.NewIDString(), Expected: scopeOf(source)}, + }, + // Nothing to compare the replacement against. Refused rather than + // applied: an unconditional whole-field write is the defect, not a + // convenience. + "no expected scope": { + input: business.UpdateSourceInput{OrgID: testOrg, ID: source.ID, Branch: "release"}, + code: codes.InvalidArgument, + }, + // The connect path's own extension rule, not a second one. + "a path is not a file suffix": { + input: business.UpdateSourceInput{OrgID: testOrg, ID: source.ID, FileExtensions: []string{"docs/*.md"}, Expected: scopeOf(source)}, + code: codes.InvalidArgument, + }, + } { + t.Run(name, func(t *testing.T) { + _, _, err := svc.UpdateSource(context.Background(), "actor-1", tc.input) + if err == nil { + t.Fatal("want an error") + } + if tc.code != codes.OK && status.Code(err) != tc.code { + t.Fatalf("code = %s, want %s", status.Code(err), tc.code) + } + }) + } + + // A source that is not there is reported as such rather than as a refusal to + // edit something: the handler maps it to NotFound. + _, _, err := svc.UpdateSource(context.Background(), "actor-1", + business.UpdateSourceInput{OrgID: testOrg, ID: business.NewIDString(), Expected: scopeOf(source)}) + if !errors.Is(err, business.ErrDatasourceSourceNotFound) { + t.Fatalf("error = %v, want ErrDatasourceSourceNotFound", err) + } +} + +// Branch and path filters are the GitHub connector's config; the other +// providers are configured by their own fields. Accepting a branch for one of +// them would report a change that changed nothing. +func TestUpdateSource_RefusesANonGitHubSource(t *testing.T) { + store := newDatasourceFakeStore() + svc, _ := newDatasourceService(store, &recordingProducer{}, nil) + // Inserted rather than connected: whether a provider is admitted for a NEW + // source is a separate decision, and this asserts the refusal for one that + // is already stored. + crawler := &business.DatasourceSource{ + ID: business.NewIDString(), + OrgID: testOrg, + Provider: business.DatasourceProviderCrawler, + Status: business.DatasourceStatusActive, + BoundaryNodeID: business.NewIDString(), + Crawler: &business.CrawlerDatasourceConfig{SitemapURL: "https://docs.example.com/sitemap.xml"}, + } + if err := store.InsertDatasourceSource(context.Background(), crawler); err != nil { + t.Fatal(err) + } + _, _, err := svc.UpdateSource(context.Background(), "actor-1", business.UpdateSourceInput{ + OrgID: testOrg, ID: crawler.ID, Branch: "main", Expected: scopeOf(crawler), + }) + if status.Code(err) != codes.FailedPrecondition { + t.Fatalf("error = %v, want FailedPrecondition", err) + } +} + +// The sync a reconfigure enqueues is a FORCED reconcile: it takes a full +// snapshot regardless of the cursor, which is what makes the new scope reach +// the consuming module instead of the compiler finding the head unmoved and +// handing nothing off. +func TestUpdateSource_ReconfiguredModeForcesTheSnapshot(t *testing.T) { + producer := &recordingProducer{} + svc, _ := newDatasourceService(newDatasourceFakeStore(), producer, nil) + source := editableSource(t, svc) + if _, _, err := svc.UpdateSource(context.Background(), "actor-1", business.UpdateSourceInput{ + OrgID: testOrg, ID: source.ID, Branch: "release", Expected: scopeOf(source), + }); err != nil { + t.Fatal(err) + } + jobs := producer.reconcileJobsFor(source.ID) + if len(jobs) != 1 { + t.Fatalf("reconcile jobs = %d, want 1", len(jobs)) + } + progress := business.ProjectDatasourceSyncProgress(business.DatasourceSyncRecord{ + JobID: "job-1", + Topic: business.DatasourceReconcileTopic, + ReconcileMode: jobs[0].GetAttributes()["datasource.reconcile_mode"], + State: jobsv1.JobState_JOB_STATE_PENDING, + }, business.DatasourceSyncHandoff{}) + if progress.Trigger != business.DatasourceSyncTriggerReconfigured { + t.Fatalf("trigger = %v, want reconfigured — a reader cannot otherwise tell this change set covers a new scope", progress.Trigger) + } +} + +// The row can be deleted between the read UpdateSource validates against and +// the statement that writes it — they are separate transactions, and under READ +// COMMITTED the second simply matches nothing. Both halves of the write path +// refuse it, and each is asserted on its own, because a caller that commits an +// audit record and queues a reconcile after an empty result does both for a +// source that is gone and answers as though the edit had landed. +// +// Deleting the row BEFORE the call would prove none of this: UpdateSource's own +// first read would answer not-found and neither statement would run. The +// deletion is interleaved into the window a caller actually has. +func TestUpdateSource_RefusesWhenTheSourceWasDeletedBeforeTheWrite(t *testing.T) { + for name, statement := range map[string]string{ + "gone by the locked read": "LockDatasourceSourceFetchScope", + "gone by the update": "UpdateDatasourceSourceFetchScope", + } { + t.Run(name, func(t *testing.T) { + store := newDatasourceFakeStore() + producer := &recordingProducer{} + svc, audit := newDatasourceService(store, producer, nil) + source := editableSource(t, svc) + store.interleave(statement, func() { store.deleteSourceRow(source.ID) }) + + _, jobID, err := svc.UpdateSource(context.Background(), "actor-1", business.UpdateSourceInput{ + OrgID: testOrg, ID: source.ID, Branch: "release", Expected: scopeOf(source), + }) + if !errors.Is(err, business.ErrDatasourceSourceNotFound) { + t.Fatalf("error = %v, want ErrDatasourceSourceNotFound", err) + } + if jobID != "" { + t.Fatalf("job id = %q, want empty", jobID) + } + if jobs := producer.reconcileJobsFor(source.ID); len(jobs) != 0 { + t.Fatalf("reconcile jobs = %d, want 0: a deleted source has nothing to re-snapshot", len(jobs)) + } + if entries := audit.entriesOf(business.EventDatasourceSourceUpdated); len(entries) != 0 { + t.Fatalf("updated events = %d, want 0: the trail must not record an edit to a source that is gone", len(entries)) + } + }) + } +} + +// Every field is a whole-field replacement, so a save states the source's +// complete scope. Applied late, that silently reverts whatever replaced what +// the editor was shown — here, someone else's narrowing — and re-ingests +// everything they had just excluded. The expectation is what refuses it. +func TestUpdateSource_RefusesAnEditWhoseExpectedScopeMoved(t *testing.T) { + store := newDatasourceFakeStore() + producer := &recordingProducer{} + svc, audit := newDatasourceService(store, producer, nil) + source := editableSource(t, svc) + opened := scopeOf(source) + + // Someone else narrows the source while the first editor's form is open. + if _, _, err := svc.UpdateSource(context.Background(), "actor-2", business.UpdateSourceInput{ + OrgID: testOrg, ID: source.ID, Branch: "main", Paths: []string{"docs/internal"}, + FileExtensions: source.FileExtensions, Expected: opened, + }); err != nil { + t.Fatal(err) + } + + // The first editor saves, carrying the scope they were shown. They only + // meant to move the branch, but their request also restates the wider paths. + _, jobID, err := svc.UpdateSource(context.Background(), "actor-1", business.UpdateSourceInput{ + OrgID: testOrg, ID: source.ID, Branch: "release", Paths: opened.Paths, + FileExtensions: opened.FileExtensions, Expected: opened, + }) + if !errors.Is(err, business.ErrDatasourceSourceScopeChanged) { + t.Fatalf("error = %v, want ErrDatasourceSourceScopeChanged", err) + } + if jobID != "" { + t.Fatalf("job id = %q, want empty", jobID) + } + + // The narrowing stands, and only the one edit that was accepted is recorded. + stored, err := svc.GetDatasourceSource(context.Background(), testOrg, source.ID) + if err != nil { + t.Fatal(err) + } + if want := []string{"docs/internal"}; !reflect.DeepEqual(stored.Paths, want) { + t.Fatalf("paths = %v, want %v: the refused save overwrote the narrowing", stored.Paths, want) + } + if stored.Branch != "main" { + t.Fatalf("branch = %q, want main: the refused save was applied", stored.Branch) + } + if entries := audit.entriesOf(business.EventDatasourceSourceUpdated); len(entries) != 1 { + t.Fatalf("updated events = %d, want 1 (the accepted edit only)", len(entries)) + } + if jobs := producer.reconcileJobsFor(source.ID); len(jobs) != 1 { + t.Fatalf("reconcile jobs = %d, want 1 (the accepted edit only)", len(jobs)) + } +} + +// Re-submitting the values a form was filled with is a no-op even when the row +// holds a spelling today's normalizers would not produce. An expectation is +// only ever compared, never stored, so holding it to the strict rule would make +// such a source impossible to edit at all. +func TestUpdateSource_ComparesAnExpectationLenientlyEnoughToEditALegacyRow(t *testing.T) { + store := newDatasourceFakeStore() + svc, _ := newDatasourceService(store, &recordingProducer{}, nil) + source := editableSource(t, svc) + // A row as an older normalizer left it: an untrimmed prefix and an uppercase + // suffix, neither of which normalizeFileExtensions would accept today. + store.setSourceScope(source.ID, "main", []string{"docs/"}, []string{".MD"}) + + updated, jobID, err := svc.UpdateSource(context.Background(), "actor-1", business.UpdateSourceInput{ + OrgID: testOrg, ID: source.ID, Branch: "main", Paths: []string{"docs", "guides"}, + FileExtensions: []string{".md"}, + Expected: &business.DatasourceFetchScope{ + Branch: "main", Paths: []string{"docs/"}, FileExtensions: []string{".MD"}, + }, + }) + if err != nil { + t.Fatalf("a legacy spelling made the source un-editable: %v", err) + } + if jobID == "" { + t.Fatal("no sync was enqueued for a change that moved what is fetched") + } + if want := []string{"docs", "guides"}; !reflect.DeepEqual(updated.Paths, want) { + t.Fatalf("paths = %v, want %v", updated.Paths, want) + } +} diff --git a/module/services/accounts/code/pkg/business/datasources_test.go b/module/services/accounts/code/pkg/business/datasources_test.go index a8688b738..9be154ded 100644 --- a/module/services/accounts/code/pkg/business/datasources_test.go +++ b/module/services/accounts/code/pkg/business/datasources_test.go @@ -42,6 +42,11 @@ type datasourceFakeStore struct { // beforeInstallationMark, when set, runs just before a park is applied. beforeInstallationMark func(sourceID string) + // before holds one-shot interleavings keyed by statement name (see + // interleave), so a test can land a concurrent change inside the window + // between a caller's read and its write. + before map[string]func() + // GitHub App onboarding: setups keyed by state hash, and the one // organization each installation is claimed by. setups map[string]*business.GitHubAppSetup @@ -137,6 +142,96 @@ func (f *datasourceFakeStore) InsertDatasourceSource(_ context.Context, source * return nil } +// UpdateDatasourceSourceFetchScope mirrors the store's narrow UPDATE: only the +// three fetch-scope fields are written, so a business change that tried to move +// a source's repository or credential through this call fails here as it would +// against the statement. +// +// Matching no row reports ErrDatasourceSourceNotFound, as the statement's empty +// command tag does. Answering nil here instead — which is what the real store +// used to do — would let a caller that writes an audit record and queues a sync +// after this call pass its tests while doing both for a source that is gone. +func (f *datasourceFakeStore) UpdateDatasourceSourceFetchScope(_ context.Context, source *business.DatasourceSource) error { + f.runBefore("UpdateDatasourceSourceFetchScope") + f.mu.Lock() + defer f.mu.Unlock() + stored, ok := f.sources[source.ID] + if !ok || stored.OrgID != source.OrgID { + return business.ErrDatasourceSourceNotFound + } + stored.Branch = source.Branch + stored.Paths = source.Paths + stored.FileExtensions = source.FileExtensions + return nil +} + +// deleteSourceRow removes a row behind the service's back, so a test can put +// the store in the state a concurrent DeleteSource leaves it in: the caller +// still holds the source it read, and the next statement matches nothing. +func (f *datasourceFakeStore) deleteSourceRow(id string) { + f.mu.Lock() + defer f.mu.Unlock() + delete(f.sources, id) +} + +// interleave runs fn once, just before the named datasource-source statement +// executes, so a test can land a concurrent change in the window a caller +// actually has rather than before the call. Deleting the row beforehand proves +// nothing: UpdateSource's own first read would answer not-found and the write +// path would never run. +func (f *datasourceFakeStore) interleave(statement string, fn func()) { + f.mu.Lock() + defer f.mu.Unlock() + if f.before == nil { + f.before = map[string]func(){} + } + f.before[statement] = fn +} + +// runBefore fires and clears a one-shot interleaving. Called with the lock +// released, because the hook it runs reaches back into the store. +func (f *datasourceFakeStore) runBefore(statement string) { + f.mu.Lock() + fn := f.before[statement] + delete(f.before, statement) + f.mu.Unlock() + if fn != nil { + fn() + } +} + +// setSourceScope writes a fetch scope straight onto the row, bypassing the +// normalizers, so a test can stand up a source spelled the way an earlier +// version of them left it. +func (f *datasourceFakeStore) setSourceScope(id, branch string, paths, extensions []string) { + f.mu.Lock() + defer f.mu.Unlock() + stored, ok := f.sources[id] + if !ok { + return + } + stored.Branch = branch + stored.Paths = paths + stored.FileExtensions = extensions +} + +// LockDatasourceSourceFetchScope mirrors the store's locked read: the row as it +// stands, or (nil, nil) when none matches. The copy is deliberate — a caller +// must not be handed the fake's own row to mutate, because the statement hands +// back a scanned snapshot and a test that shared memory with the store would +// pass an expectation check that production fails. +func (f *datasourceFakeStore) LockDatasourceSourceFetchScope(_ context.Context, orgID, id string) (*business.DatasourceSource, error) { + f.runBefore("LockDatasourceSourceFetchScope") + f.mu.Lock() + defer f.mu.Unlock() + stored, ok := f.sources[id] + if !ok || stored.OrgID != orgID { + return nil, nil + } + cp := *stored + return &cp, nil +} + func (f *datasourceFakeStore) ListDatasourceSources(_ context.Context, orgID string) ([]*business.DatasourceSource, error) { f.mu.Lock() defer f.mu.Unlock() @@ -511,6 +606,21 @@ type recordingProducer struct { jobs []*jobsv1.NewJob } +// reconcileJobsFor returns the reconcile jobs enqueued for one source, so a +// test can assert the mode a pass ran in rather than that something was queued. +func (p *recordingProducer) reconcileJobsFor(sourceID string) []*jobsv1.NewJob { + p.mu.Lock() + defer p.mu.Unlock() + var out []*jobsv1.NewJob + for _, job := range p.jobs { + if job.GetTopic() == business.DatasourceReconcileTopic && + job.GetAttributes()["datasource.source_id"] == sourceID { + out = append(out, job) + } + } + return out +} + func (p *recordingProducer) EnqueueJob(_ context.Context, req *jobsv1.EnqueueJobRequest) (*jobsv1.EnqueueJobResponse, error) { // Enforce the same command contract the real producer does — PostgresJobStore // validates through jobs.EnqueueFingerprint before a statement is ever sent — diff --git a/module/services/accounts/code/pkg/business/introspection.go b/module/services/accounts/code/pkg/business/introspection.go index f3dbd53ae..86142d347 100644 --- a/module/services/accounts/code/pkg/business/introspection.go +++ b/module/services/accounts/code/pkg/business/introspection.go @@ -147,6 +147,7 @@ var rpcDescriptions = withWorkContextConsumerDescriptions(map[string]string{ "DatasourceService/GetSource": "Read one connected datasource in the org.", "DatasourceService/ListSources": "List the org's connected datasources.", "DatasourceService/SyncSource": "Pull the source's current contents and enqueue ingestion deliveries.", + "DatasourceService/UpdateSource": "Change what a connected source fetches — its branch and path and file-type filters — keeping its credential, history and collection.", "DatasourceService/GetSourceSync": "Read one source sync's typed stages, durable lifecycle and module-reported execution references; with no job id, the source's latest sync.", "DelegationService/DecideDelegation": "Approve or deny a delegation request.", "InstallationService/InstallSolution": "Install a solution: compose its agent principal, scope node, standing grant, and installation row.", diff --git a/module/services/accounts/code/pkg/business/store.go b/module/services/accounts/code/pkg/business/store.go index a30ce6c22..58545494d 100644 --- a/module/services/accounts/code/pkg/business/store.go +++ b/module/services/accounts/code/pkg/business/store.go @@ -105,6 +105,17 @@ type Store interface { // Call it at the top level only — nesting it inside an existing // WithOrgTx/WithControlPlane violates the no-nesting rule. InsertDatasourceSource(ctx context.Context, source *DatasourceSource) error + // UpdateDatasourceSourceFetchScope replaces what a Source fetches — branch, + // paths and the config envelope carrying its file-type filter — and nothing + // else. Reports ErrDatasourceSourceNotFound when no row matched, so a source + // deleted since the caller read it cannot be answered as a successful edit. + // MUST run inside the caller's WithOrgTx. + UpdateDatasourceSourceFetchScope(ctx context.Context, source *DatasourceSource) error + // LockDatasourceSourceFetchScope reads one Source under a row lock, or + // (nil, nil) when none matches, so an edit compares the scope it expects + // against the row as it is at the moment it writes. MUST run inside the + // caller's WithOrgTx. + LockDatasourceSourceFetchScope(ctx context.Context, orgID, id string) (*DatasourceSource, error) WithSourceReadSnapshot(context.Context, string, func(context.Context) error) error SourceReadRevision(context.Context, string, []string) (string, time.Time, error) ListReadableSourcesPage(context.Context, string, []string, []string, string, int) ([]*gen.ReadableSourceCollection, error) diff --git a/module/services/accounts/code/pkg/eventcatalog/catalog_gen.go b/module/services/accounts/code/pkg/eventcatalog/catalog_gen.go index 40113d826..81deb2737 100644 --- a/module/services/accounts/code/pkg/eventcatalog/catalog_gen.go +++ b/module/services/accounts/code/pkg/eventcatalog/catalog_gen.go @@ -83,6 +83,7 @@ var published = [...]PublishedEvent{ {Type: "saas.datasource.source.recovered", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.datasource.source.removed", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.datasource.source.synced", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, + {Type: "saas.datasource.source.updated", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.datasource.sync.completed", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.datasource.sync.failed", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, {Type: "saas.delegation.approved", Namespace: "saas", Schema: "saas/events/v1/events.proto#EventEnvelope", Major: 1, Visibility: "external", Partition: "", Retention: "30d"}, 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 d299dd5c0..773f6797d 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 @@ -58,6 +58,9 @@ const ( // DatasourceServiceAddSourceProcedure is the fully-qualified name of the DatasourceService's // AddSource RPC. DatasourceServiceAddSourceProcedure = "/saas.accounts.v1.DatasourceService/AddSource" + // DatasourceServiceUpdateSourceProcedure is the fully-qualified name of the DatasourceService's + // UpdateSource RPC. + DatasourceServiceUpdateSourceProcedure = "/saas.accounts.v1.DatasourceService/UpdateSource" // DatasourceServiceGetDatasourceCatalogProcedure is the fully-qualified name of the // DatasourceService's GetDatasourceCatalog RPC. DatasourceServiceGetDatasourceCatalogProcedure = "/saas.accounts.v1.DatasourceService/GetDatasourceCatalog" @@ -171,6 +174,12 @@ type DatasourceServiceClient interface { // by provider, stores the config and the encrypted credential (and optional // webhook signing secret), and returns the non-secret projection. AddSource(context.Context, *connect.Request[v1.AddSourceRequest]) (*connect.Response[v1.AddSourceResponse], error) + // UpdateSource changes what an already-connected source fetches — its branch + // and its path and file-type filters — keeping its identity, its credential, + // its sync history and the collection it writes into. A change that moves + // what is fetched enqueues a sync, whose progress reports the reconfigured + // trigger so a reader can tell a re-snapshot from an ordinary one. + UpdateSource(context.Context, *connect.Request[v1.UpdateSourceRequest]) (*connect.Response[v1.UpdateSourceResponse], error) // GetDatasourceCatalog returns the host's connector registry: every provider, // its connect metadata, whether it conforms to the datasource connector // envelope, and whether it accepts a new source, so a client can render the @@ -353,6 +362,12 @@ func NewDatasourceServiceClient(httpClient connect.HTTPClient, baseURL string, o connect.WithSchema(datasourceServiceMethods.ByName("AddSource")), connect.WithClientOptions(opts...), ), + updateSource: connect.NewClient[v1.UpdateSourceRequest, v1.UpdateSourceResponse]( + httpClient, + baseURL+DatasourceServiceUpdateSourceProcedure, + connect.WithSchema(datasourceServiceMethods.ByName("UpdateSource")), + connect.WithClientOptions(opts...), + ), getDatasourceCatalog: connect.NewClient[v1.GetDatasourceCatalogRequest, v1.GetDatasourceCatalogResponse]( httpClient, baseURL+DatasourceServiceGetDatasourceCatalogProcedure, @@ -516,6 +531,7 @@ type datasourceServiceClient struct { declareSourceOperations *connect.Client[v1.DeclareSourceOperationsRequest, v1.DeclareSourceOperationsResponse] addGitHubSource *connect.Client[v1.AddGitHubSourceRequest, v1.AddGitHubSourceResponse] addSource *connect.Client[v1.AddSourceRequest, v1.AddSourceResponse] + updateSource *connect.Client[v1.UpdateSourceRequest, v1.UpdateSourceResponse] getDatasourceCatalog *connect.Client[v1.GetDatasourceCatalogRequest, v1.GetDatasourceCatalogResponse] listSources *connect.Client[v1.ListSourcesRequest, v1.ListSourcesResponse] getSource *connect.Client[v1.GetSourceRequest, v1.GetSourceResponse] @@ -585,6 +601,11 @@ func (c *datasourceServiceClient) AddSource(ctx context.Context, req *connect.Re return c.addSource.CallUnary(ctx, req) } +// UpdateSource calls saas.accounts.v1.DatasourceService.UpdateSource. +func (c *datasourceServiceClient) UpdateSource(ctx context.Context, req *connect.Request[v1.UpdateSourceRequest]) (*connect.Response[v1.UpdateSourceResponse], error) { + return c.updateSource.CallUnary(ctx, req) +} + // GetDatasourceCatalog calls saas.accounts.v1.DatasourceService.GetDatasourceCatalog. func (c *datasourceServiceClient) GetDatasourceCatalog(ctx context.Context, req *connect.Request[v1.GetDatasourceCatalogRequest]) (*connect.Response[v1.GetDatasourceCatalogResponse], error) { return c.getDatasourceCatalog.CallUnary(ctx, req) @@ -748,6 +769,12 @@ type DatasourceServiceHandler interface { // by provider, stores the config and the encrypted credential (and optional // webhook signing secret), and returns the non-secret projection. AddSource(context.Context, *connect.Request[v1.AddSourceRequest]) (*connect.Response[v1.AddSourceResponse], error) + // UpdateSource changes what an already-connected source fetches — its branch + // and its path and file-type filters — keeping its identity, its credential, + // its sync history and the collection it writes into. A change that moves + // what is fetched enqueues a sync, whose progress reports the reconfigured + // trigger so a reader can tell a re-snapshot from an ordinary one. + UpdateSource(context.Context, *connect.Request[v1.UpdateSourceRequest]) (*connect.Response[v1.UpdateSourceResponse], error) // GetDatasourceCatalog returns the host's connector registry: every provider, // its connect metadata, whether it conforms to the datasource connector // envelope, and whether it accepts a new source, so a client can render the @@ -926,6 +953,12 @@ func NewDatasourceServiceHandler(svc DatasourceServiceHandler, opts ...connect.H connect.WithSchema(datasourceServiceMethods.ByName("AddSource")), connect.WithHandlerOptions(opts...), ) + datasourceServiceUpdateSourceHandler := connect.NewUnaryHandler( + DatasourceServiceUpdateSourceProcedure, + svc.UpdateSource, + connect.WithSchema(datasourceServiceMethods.ByName("UpdateSource")), + connect.WithHandlerOptions(opts...), + ) datasourceServiceGetDatasourceCatalogHandler := connect.NewUnaryHandler( DatasourceServiceGetDatasourceCatalogProcedure, svc.GetDatasourceCatalog, @@ -1094,6 +1127,8 @@ func NewDatasourceServiceHandler(svc DatasourceServiceHandler, opts ...connect.H datasourceServiceAddGitHubSourceHandler.ServeHTTP(w, r) case DatasourceServiceAddSourceProcedure: datasourceServiceAddSourceHandler.ServeHTTP(w, r) + case DatasourceServiceUpdateSourceProcedure: + datasourceServiceUpdateSourceHandler.ServeHTTP(w, r) case DatasourceServiceGetDatasourceCatalogProcedure: datasourceServiceGetDatasourceCatalogHandler.ServeHTTP(w, r) case DatasourceServiceListSourcesProcedure: @@ -1185,6 +1220,10 @@ func (UnimplementedDatasourceServiceHandler) AddSource(context.Context, *connect return nil, connect.NewError(connect.CodeUnimplemented, errors.New("saas.accounts.v1.DatasourceService.AddSource is not implemented")) } +func (UnimplementedDatasourceServiceHandler) UpdateSource(context.Context, *connect.Request[v1.UpdateSourceRequest]) (*connect.Response[v1.UpdateSourceResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("saas.accounts.v1.DatasourceService.UpdateSource is not implemented")) +} + func (UnimplementedDatasourceServiceHandler) GetDatasourceCatalog(context.Context, *connect.Request[v1.GetDatasourceCatalogRequest]) (*connect.Response[v1.GetDatasourceCatalogResponse], error) { return nil, connect.NewError(connect.CodeUnimplemented, errors.New("saas.accounts.v1.DatasourceService.GetDatasourceCatalog 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 c7a189207..c223254c6 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 @@ -643,6 +643,11 @@ const ( SourceSyncTrigger_SOURCE_SYNC_TRIGGER_SCHEDULED SourceSyncTrigger = 2 // A webhook delivery from the provider. SourceSyncTrigger_SOURCE_SYNC_TRIGGER_WEBHOOK SourceSyncTrigger = 3 + // The source was reconfigured — its branch or its path/file-type filters + // changed — so this sync re-snapshots it at the new scope. It is the one + // trigger that tells a reader the change set is large because the source + // moved rather than because the repository did. + SourceSyncTrigger_SOURCE_SYNC_TRIGGER_RECONFIGURED SourceSyncTrigger = 4 ) // Enum value maps for SourceSyncTrigger. @@ -652,12 +657,14 @@ var ( 1: "SOURCE_SYNC_TRIGGER_MANUAL", 2: "SOURCE_SYNC_TRIGGER_SCHEDULED", 3: "SOURCE_SYNC_TRIGGER_WEBHOOK", + 4: "SOURCE_SYNC_TRIGGER_RECONFIGURED", } SourceSyncTrigger_value = map[string]int32{ - "SOURCE_SYNC_TRIGGER_UNSPECIFIED": 0, - "SOURCE_SYNC_TRIGGER_MANUAL": 1, - "SOURCE_SYNC_TRIGGER_SCHEDULED": 2, - "SOURCE_SYNC_TRIGGER_WEBHOOK": 3, + "SOURCE_SYNC_TRIGGER_UNSPECIFIED": 0, + "SOURCE_SYNC_TRIGGER_MANUAL": 1, + "SOURCE_SYNC_TRIGGER_SCHEDULED": 2, + "SOURCE_SYNC_TRIGGER_WEBHOOK": 3, + "SOURCE_SYNC_TRIGGER_RECONFIGURED": 4, } ) @@ -914,7 +921,7 @@ func (x SourceOperation_Effect) Number() protoreflect.EnumNumber { // Deprecated: Use SourceOperation_Effect.Descriptor instead. func (SourceOperation_Effect) EnumDescriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{77, 0} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{80, 0} } // GitHubDatasourceConfig is the public, non-secret configuration of a GitHub @@ -2035,6 +2042,250 @@ func (x *AddSourceResponse) GetDatasource() *Datasource { return nil } +// UpdateSourceRequest changes what an already-connected source fetches, in +// place: its branch and its path and file-type filters. Nothing else about a +// source is mutable here — the repository, the provider, the collection it +// writes into and its credential are each the identity of something a consumer +// already holds, and changing one of those is a different source. +// +// Every field is a WHOLE-FIELD REPLACEMENT, not a patch: the source is left +// fetching exactly what this request names, so an empty `paths` ingests the +// whole repository and an empty `file_extensions` admits every file type, +// exactly as they mean on connect. A repeated proto3 field cannot tell "not +// sent" from "sent empty", so a request that meant to leave a filter alone +// must repeat it; naming that here is what keeps a clearing from reading as an +// omission, and an omission from being silently preserved. +// +// Because every field is a whole-field replacement, every request asserts the +// source's complete scope — so one is accepted only against the scope its +// author was actually shown. `expected` carries that scope back, and a request +// whose expectation no longer matches the stored row is REFUSED rather than +// applied: without it, a form opened while the source read one way and saved +// after someone else narrowed it would silently restore the wider scope and +// re-ingest everything the other person had just excluded. +type UpdateSourceRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + OrgId string `protobuf:"bytes,1,opt,name=org_id,json=orgId,proto3" json:"org_id,omitempty"` + Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` + // Git ref (branch) to pull. Empty resolves to the repository default branch, + // as on connect. A branch the source's own credential cannot read is refused + // before anything is written. + Branch string `protobuf:"bytes,3,opt,name=branch,proto3" json:"branch,omitempty"` + // Repository-relative path prefixes to ingest. Empty ingests the whole repo. + Paths []string `protobuf:"bytes,4,rep,name=paths,proto3" json:"paths,omitempty"` + // Case-insensitive file suffixes, e.g. [".md", ".mdx"]. Empty admits every + // file type within paths. + FileExtensions []string `protobuf:"bytes,5,rep,name=file_extensions,json=fileExtensions,proto3" json:"file_extensions,omitempty"` + // The scope the caller was shown, as it read it. Required: an unconditional + // whole-field replacement is exactly how one editor's save erases another's, + // so there is no "just apply it" mode to fall back to. + // + // It is the scope and not the row's `updated_at` because `updated_at` moves + // on every successful sync, which would refuse a perfectly good edit for the + // sole reason that the source had reconciled since the form was opened. What + // an editor needs guarded is what they were looking at. + Expected *SourceFetchScope `protobuf:"bytes,6,opt,name=expected,proto3" json:"expected,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateSourceRequest) Reset() { + *x = UpdateSourceRequest{} + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateSourceRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateSourceRequest) ProtoMessage() {} + +func (x *UpdateSourceRequest) ProtoReflect() protoreflect.Message { + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateSourceRequest.ProtoReflect.Descriptor instead. +func (*UpdateSourceRequest) Descriptor() ([]byte, []int) { + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{10} +} + +func (x *UpdateSourceRequest) GetOrgId() string { + if x != nil { + return x.OrgId + } + return "" +} + +func (x *UpdateSourceRequest) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *UpdateSourceRequest) GetBranch() string { + if x != nil { + return x.Branch + } + return "" +} + +func (x *UpdateSourceRequest) GetPaths() []string { + if x != nil { + return x.Paths + } + return nil +} + +func (x *UpdateSourceRequest) GetFileExtensions() []string { + if x != nil { + return x.FileExtensions + } + return nil +} + +func (x *UpdateSourceRequest) GetExpected() *SourceFetchScope { + if x != nil { + return x.Expected + } + return nil +} + +// SourceFetchScope is what a source fetches — the three values UpdateSource +// replaces — carried on its own so a request can state the scope it expects to +// be replacing as well as the one it wants. +// +// Its bounds are deliberately looser than the mutable fields': an expectation +// is only ever compared against the stored row, never stored, so holding it to +// today's shape would make a source that predates that shape impossible to +// edit at all. Size is still bounded, because the comparison reads the whole +// thing. +type SourceFetchScope struct { + state protoimpl.MessageState `protogen:"open.v1"` + Branch string `protobuf:"bytes,1,opt,name=branch,proto3" json:"branch,omitempty"` + Paths []string `protobuf:"bytes,2,rep,name=paths,proto3" json:"paths,omitempty"` + FileExtensions []string `protobuf:"bytes,3,rep,name=file_extensions,json=fileExtensions,proto3" json:"file_extensions,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SourceFetchScope) Reset() { + *x = SourceFetchScope{} + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SourceFetchScope) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SourceFetchScope) ProtoMessage() {} + +func (x *SourceFetchScope) ProtoReflect() protoreflect.Message { + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SourceFetchScope.ProtoReflect.Descriptor instead. +func (*SourceFetchScope) Descriptor() ([]byte, []int) { + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{11} +} + +func (x *SourceFetchScope) GetBranch() string { + if x != nil { + return x.Branch + } + return "" +} + +func (x *SourceFetchScope) GetPaths() []string { + if x != nil { + return x.Paths + } + return nil +} + +func (x *SourceFetchScope) GetFileExtensions() []string { + if x != nil { + return x.FileExtensions + } + return nil +} + +type UpdateSourceResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + Datasource *Datasource `protobuf:"bytes,1,opt,name=datasource,proto3" json:"datasource,omitempty"` + // The sync enqueued because the update moved what the source fetches, which + // for a consumer is a new snapshot of the collection. Empty when the request + // changed nothing, which is the only case where no sync runs: every mutable + // field here selects content, so any real change re-snapshots the source. + JobId string `protobuf:"bytes,2,opt,name=job_id,json=jobId,proto3" json:"job_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateSourceResponse) Reset() { + *x = UpdateSourceResponse{} + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateSourceResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateSourceResponse) ProtoMessage() {} + +func (x *UpdateSourceResponse) ProtoReflect() protoreflect.Message { + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateSourceResponse.ProtoReflect.Descriptor instead. +func (*UpdateSourceResponse) Descriptor() ([]byte, []int) { + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{12} +} + +func (x *UpdateSourceResponse) GetDatasource() *Datasource { + if x != nil { + return x.Datasource + } + return nil +} + +func (x *UpdateSourceResponse) GetJobId() string { + if x != nil { + return x.JobId + } + return "" +} + // DatasourceConfigField describes one non-secret config input a provider needs, // so the UI can render the connect form from the catalog rather than hardcoding // it per provider. @@ -2051,7 +2302,7 @@ type DatasourceConfigField struct { func (x *DatasourceConfigField) Reset() { *x = DatasourceConfigField{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[10] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[13] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2063,7 +2314,7 @@ func (x *DatasourceConfigField) String() string { func (*DatasourceConfigField) ProtoMessage() {} func (x *DatasourceConfigField) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[10] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[13] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2076,7 +2327,7 @@ func (x *DatasourceConfigField) ProtoReflect() protoreflect.Message { // Deprecated: Use DatasourceConfigField.ProtoReflect.Descriptor instead. func (*DatasourceConfigField) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{10} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{13} } func (x *DatasourceConfigField) GetKey() string { @@ -2125,7 +2376,7 @@ type DatasourceConnectorBudget struct { func (x *DatasourceConnectorBudget) Reset() { *x = DatasourceConnectorBudget{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[11] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[14] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2137,7 +2388,7 @@ func (x *DatasourceConnectorBudget) String() string { func (*DatasourceConnectorBudget) ProtoMessage() {} func (x *DatasourceConnectorBudget) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[11] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[14] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2150,7 +2401,7 @@ func (x *DatasourceConnectorBudget) ProtoReflect() protoreflect.Message { // Deprecated: Use DatasourceConnectorBudget.ProtoReflect.Descriptor instead. func (*DatasourceConnectorBudget) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{11} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{14} } func (x *DatasourceConnectorBudget) GetMaxItemsPerCall() uint32 { @@ -2245,7 +2496,7 @@ type DatasourceProviderDescriptor struct { func (x *DatasourceProviderDescriptor) Reset() { *x = DatasourceProviderDescriptor{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[12] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[15] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2257,7 +2508,7 @@ func (x *DatasourceProviderDescriptor) String() string { func (*DatasourceProviderDescriptor) ProtoMessage() {} func (x *DatasourceProviderDescriptor) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[12] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[15] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2270,7 +2521,7 @@ func (x *DatasourceProviderDescriptor) ProtoReflect() protoreflect.Message { // Deprecated: Use DatasourceProviderDescriptor.ProtoReflect.Descriptor instead. func (*DatasourceProviderDescriptor) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{12} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{15} } func (x *DatasourceProviderDescriptor) GetProvider() DatasourceProvider { @@ -2421,7 +2672,7 @@ type GetDatasourceCatalogRequest struct { func (x *GetDatasourceCatalogRequest) Reset() { *x = GetDatasourceCatalogRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[13] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[16] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2433,7 +2684,7 @@ func (x *GetDatasourceCatalogRequest) String() string { func (*GetDatasourceCatalogRequest) ProtoMessage() {} func (x *GetDatasourceCatalogRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[13] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[16] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2446,7 +2697,7 @@ func (x *GetDatasourceCatalogRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDatasourceCatalogRequest.ProtoReflect.Descriptor instead. func (*GetDatasourceCatalogRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{13} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{16} } type GetDatasourceCatalogResponse struct { @@ -2458,7 +2709,7 @@ type GetDatasourceCatalogResponse struct { func (x *GetDatasourceCatalogResponse) Reset() { *x = GetDatasourceCatalogResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[14] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[17] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2470,7 +2721,7 @@ func (x *GetDatasourceCatalogResponse) String() string { func (*GetDatasourceCatalogResponse) ProtoMessage() {} func (x *GetDatasourceCatalogResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[14] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[17] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2483,7 +2734,7 @@ func (x *GetDatasourceCatalogResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDatasourceCatalogResponse.ProtoReflect.Descriptor instead. func (*GetDatasourceCatalogResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{14} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{17} } func (x *GetDatasourceCatalogResponse) GetProviders() []*DatasourceProviderDescriptor { @@ -2502,7 +2753,7 @@ type ListSourcesRequest struct { func (x *ListSourcesRequest) Reset() { *x = ListSourcesRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[15] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[18] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2514,7 +2765,7 @@ func (x *ListSourcesRequest) String() string { func (*ListSourcesRequest) ProtoMessage() {} func (x *ListSourcesRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[15] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[18] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2527,7 +2778,7 @@ func (x *ListSourcesRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSourcesRequest.ProtoReflect.Descriptor instead. func (*ListSourcesRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{15} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{18} } func (x *ListSourcesRequest) GetOrgId() string { @@ -2546,7 +2797,7 @@ type ListSourcesResponse struct { func (x *ListSourcesResponse) Reset() { *x = ListSourcesResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[16] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[19] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2558,7 +2809,7 @@ func (x *ListSourcesResponse) String() string { func (*ListSourcesResponse) ProtoMessage() {} func (x *ListSourcesResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[16] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[19] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2571,7 +2822,7 @@ func (x *ListSourcesResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSourcesResponse.ProtoReflect.Descriptor instead. func (*ListSourcesResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{16} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{19} } func (x *ListSourcesResponse) GetDatasources() []*Datasource { @@ -2591,7 +2842,7 @@ type GetSourceRequest struct { func (x *GetSourceRequest) Reset() { *x = GetSourceRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[17] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[20] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2603,7 +2854,7 @@ func (x *GetSourceRequest) String() string { func (*GetSourceRequest) ProtoMessage() {} func (x *GetSourceRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[17] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[20] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2616,7 +2867,7 @@ func (x *GetSourceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSourceRequest.ProtoReflect.Descriptor instead. func (*GetSourceRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{17} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{20} } func (x *GetSourceRequest) GetOrgId() string { @@ -2642,7 +2893,7 @@ type GetSourceResponse struct { func (x *GetSourceResponse) Reset() { *x = GetSourceResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[18] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[21] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2654,7 +2905,7 @@ func (x *GetSourceResponse) String() string { func (*GetSourceResponse) ProtoMessage() {} func (x *GetSourceResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[18] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[21] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2667,7 +2918,7 @@ func (x *GetSourceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSourceResponse.ProtoReflect.Descriptor instead. func (*GetSourceResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{18} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{21} } func (x *GetSourceResponse) GetDatasource() *Datasource { @@ -2698,7 +2949,7 @@ type SyncSourceRequest struct { func (x *SyncSourceRequest) Reset() { *x = SyncSourceRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[19] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[22] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2710,7 +2961,7 @@ func (x *SyncSourceRequest) String() string { func (*SyncSourceRequest) ProtoMessage() {} func (x *SyncSourceRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[19] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[22] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2723,7 +2974,7 @@ func (x *SyncSourceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use SyncSourceRequest.ProtoReflect.Descriptor instead. func (*SyncSourceRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{19} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{22} } func (x *SyncSourceRequest) GetOrgId() string { @@ -2765,7 +3016,7 @@ type SyncSourceResponse struct { func (x *SyncSourceResponse) Reset() { *x = SyncSourceResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[20] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[23] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2777,7 +3028,7 @@ func (x *SyncSourceResponse) String() string { func (*SyncSourceResponse) ProtoMessage() {} func (x *SyncSourceResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[20] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[23] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2790,7 +3041,7 @@ func (x *SyncSourceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use SyncSourceResponse.ProtoReflect.Descriptor instead. func (*SyncSourceResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{20} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{23} } func (x *SyncSourceResponse) GetJobId() string { @@ -2815,7 +3066,7 @@ type GetSourceSyncRequest struct { func (x *GetSourceSyncRequest) Reset() { *x = GetSourceSyncRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[21] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[24] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2827,7 +3078,7 @@ func (x *GetSourceSyncRequest) String() string { func (*GetSourceSyncRequest) ProtoMessage() {} func (x *GetSourceSyncRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[21] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[24] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2840,7 +3091,7 @@ func (x *GetSourceSyncRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSourceSyncRequest.ProtoReflect.Descriptor instead. func (*GetSourceSyncRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{21} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{24} } func (x *GetSourceSyncRequest) GetOrgId() string { @@ -2875,7 +3126,7 @@ type SourceSyncDelivery struct { func (x *SourceSyncDelivery) Reset() { *x = SourceSyncDelivery{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[22] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[25] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2887,7 +3138,7 @@ func (x *SourceSyncDelivery) String() string { func (*SourceSyncDelivery) ProtoMessage() {} func (x *SourceSyncDelivery) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[22] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[25] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2900,7 +3151,7 @@ func (x *SourceSyncDelivery) ProtoReflect() protoreflect.Message { // Deprecated: Use SourceSyncDelivery.ProtoReflect.Descriptor instead. func (*SourceSyncDelivery) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{22} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{25} } func (x *SourceSyncDelivery) GetJobId() string { @@ -2948,7 +3199,7 @@ type SourceSyncChanges struct { func (x *SourceSyncChanges) Reset() { *x = SourceSyncChanges{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[23] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[26] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -2960,7 +3211,7 @@ func (x *SourceSyncChanges) String() string { func (*SourceSyncChanges) ProtoMessage() {} func (x *SourceSyncChanges) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[23] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[26] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2973,7 +3224,7 @@ func (x *SourceSyncChanges) ProtoReflect() protoreflect.Message { // Deprecated: Use SourceSyncChanges.ProtoReflect.Descriptor instead. func (*SourceSyncChanges) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{23} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{26} } func (x *SourceSyncChanges) GetFiles() uint32 { @@ -3044,7 +3295,7 @@ type SourceSyncFailure struct { func (x *SourceSyncFailure) Reset() { *x = SourceSyncFailure{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[24] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[27] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3056,7 +3307,7 @@ func (x *SourceSyncFailure) String() string { func (*SourceSyncFailure) ProtoMessage() {} func (x *SourceSyncFailure) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[24] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[27] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3069,7 +3320,7 @@ func (x *SourceSyncFailure) ProtoReflect() protoreflect.Message { // Deprecated: Use SourceSyncFailure.ProtoReflect.Descriptor instead. func (*SourceSyncFailure) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{24} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{27} } func (x *SourceSyncFailure) GetReason() SourceSyncFailureReason { @@ -3137,7 +3388,7 @@ type SourceSyncProgress struct { func (x *SourceSyncProgress) Reset() { *x = SourceSyncProgress{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[25] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[28] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3149,7 +3400,7 @@ func (x *SourceSyncProgress) String() string { func (*SourceSyncProgress) ProtoMessage() {} func (x *SourceSyncProgress) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[25] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[28] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3162,7 +3413,7 @@ func (x *SourceSyncProgress) ProtoReflect() protoreflect.Message { // Deprecated: Use SourceSyncProgress.ProtoReflect.Descriptor instead. func (*SourceSyncProgress) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{25} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{28} } func (x *SourceSyncProgress) GetPhase() SourceSyncPhase { @@ -3254,7 +3505,7 @@ type GetSourceSyncResponse struct { func (x *GetSourceSyncResponse) Reset() { *x = GetSourceSyncResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[26] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[29] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3266,7 +3517,7 @@ func (x *GetSourceSyncResponse) String() string { func (*GetSourceSyncResponse) ProtoMessage() {} func (x *GetSourceSyncResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[26] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[29] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3279,7 +3530,7 @@ func (x *GetSourceSyncResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetSourceSyncResponse.ProtoReflect.Descriptor instead. func (*GetSourceSyncResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{26} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{29} } func (x *GetSourceSyncResponse) GetJobId() string { @@ -3320,7 +3571,7 @@ type DeleteSourceRequest struct { func (x *DeleteSourceRequest) Reset() { *x = DeleteSourceRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[27] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[30] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3332,7 +3583,7 @@ func (x *DeleteSourceRequest) String() string { func (*DeleteSourceRequest) ProtoMessage() {} func (x *DeleteSourceRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[27] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[30] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3345,7 +3596,7 @@ func (x *DeleteSourceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteSourceRequest.ProtoReflect.Descriptor instead. func (*DeleteSourceRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{27} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{30} } func (x *DeleteSourceRequest) GetOrgId() string { @@ -3370,7 +3621,7 @@ type DeleteSourceResponse struct { func (x *DeleteSourceResponse) Reset() { *x = DeleteSourceResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[28] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[31] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3382,7 +3633,7 @@ func (x *DeleteSourceResponse) String() string { func (*DeleteSourceResponse) ProtoMessage() {} func (x *DeleteSourceResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[28] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[31] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3395,7 +3646,7 @@ func (x *DeleteSourceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteSourceResponse.ProtoReflect.Descriptor instead. func (*DeleteSourceResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{28} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{31} } // GitHubAppRepository is one repository a verified App installation grants this @@ -3416,7 +3667,7 @@ type GitHubAppRepository struct { func (x *GitHubAppRepository) Reset() { *x = GitHubAppRepository{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[29] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[32] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3428,7 +3679,7 @@ func (x *GitHubAppRepository) String() string { func (*GitHubAppRepository) ProtoMessage() {} func (x *GitHubAppRepository) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[29] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[32] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3441,7 +3692,7 @@ func (x *GitHubAppRepository) ProtoReflect() protoreflect.Message { // Deprecated: Use GitHubAppRepository.ProtoReflect.Descriptor instead. func (*GitHubAppRepository) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{29} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{32} } func (x *GitHubAppRepository) GetRepo() string { @@ -3474,7 +3725,7 @@ type BeginGitHubAppSetupRequest struct { func (x *BeginGitHubAppSetupRequest) Reset() { *x = BeginGitHubAppSetupRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[30] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[33] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3486,7 +3737,7 @@ func (x *BeginGitHubAppSetupRequest) String() string { func (*BeginGitHubAppSetupRequest) ProtoMessage() {} func (x *BeginGitHubAppSetupRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[30] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[33] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3499,7 +3750,7 @@ func (x *BeginGitHubAppSetupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BeginGitHubAppSetupRequest.ProtoReflect.Descriptor instead. func (*BeginGitHubAppSetupRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{30} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{33} } func (x *BeginGitHubAppSetupRequest) GetOrgId() string { @@ -3526,7 +3777,7 @@ type BeginGitHubAppSetupResponse struct { func (x *BeginGitHubAppSetupResponse) Reset() { *x = BeginGitHubAppSetupResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[31] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[34] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3538,7 +3789,7 @@ func (x *BeginGitHubAppSetupResponse) String() string { func (*BeginGitHubAppSetupResponse) ProtoMessage() {} func (x *BeginGitHubAppSetupResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[31] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[34] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3551,7 +3802,7 @@ func (x *BeginGitHubAppSetupResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BeginGitHubAppSetupResponse.ProtoReflect.Descriptor instead. func (*BeginGitHubAppSetupResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{31} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{34} } func (x *BeginGitHubAppSetupResponse) GetInstallUrl() string { @@ -3600,7 +3851,7 @@ type CompleteGitHubAppSetupRequest struct { func (x *CompleteGitHubAppSetupRequest) Reset() { *x = CompleteGitHubAppSetupRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[32] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[35] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3612,7 +3863,7 @@ func (x *CompleteGitHubAppSetupRequest) String() string { func (*CompleteGitHubAppSetupRequest) ProtoMessage() {} func (x *CompleteGitHubAppSetupRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[32] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[35] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3625,7 +3876,7 @@ func (x *CompleteGitHubAppSetupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use CompleteGitHubAppSetupRequest.ProtoReflect.Descriptor instead. func (*CompleteGitHubAppSetupRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{32} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{35} } func (x *CompleteGitHubAppSetupRequest) GetOrgId() string { @@ -3668,7 +3919,7 @@ type CompleteGitHubAppSetupResponse struct { func (x *CompleteGitHubAppSetupResponse) Reset() { *x = CompleteGitHubAppSetupResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[33] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[36] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3680,7 +3931,7 @@ func (x *CompleteGitHubAppSetupResponse) String() string { func (*CompleteGitHubAppSetupResponse) ProtoMessage() {} func (x *CompleteGitHubAppSetupResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[33] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[36] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3693,7 +3944,7 @@ func (x *CompleteGitHubAppSetupResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use CompleteGitHubAppSetupResponse.ProtoReflect.Descriptor instead. func (*CompleteGitHubAppSetupResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{33} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{36} } func (x *CompleteGitHubAppSetupResponse) GetInstallationId() string { @@ -3720,7 +3971,7 @@ type MigrateGitHubSourceToAppRequest struct { func (x *MigrateGitHubSourceToAppRequest) Reset() { *x = MigrateGitHubSourceToAppRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[34] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[37] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3732,7 +3983,7 @@ func (x *MigrateGitHubSourceToAppRequest) String() string { func (*MigrateGitHubSourceToAppRequest) ProtoMessage() {} func (x *MigrateGitHubSourceToAppRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[34] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[37] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3745,7 +3996,7 @@ func (x *MigrateGitHubSourceToAppRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use MigrateGitHubSourceToAppRequest.ProtoReflect.Descriptor instead. func (*MigrateGitHubSourceToAppRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{34} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{37} } func (x *MigrateGitHubSourceToAppRequest) GetOrgId() string { @@ -3771,7 +4022,7 @@ type MigrateGitHubSourceToAppResponse struct { func (x *MigrateGitHubSourceToAppResponse) Reset() { *x = MigrateGitHubSourceToAppResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[35] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[38] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3783,7 +4034,7 @@ func (x *MigrateGitHubSourceToAppResponse) String() string { func (*MigrateGitHubSourceToAppResponse) ProtoMessage() {} func (x *MigrateGitHubSourceToAppResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[35] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[38] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3796,7 +4047,7 @@ func (x *MigrateGitHubSourceToAppResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use MigrateGitHubSourceToAppResponse.ProtoReflect.Descriptor instead. func (*MigrateGitHubSourceToAppResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{35} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{38} } func (x *MigrateGitHubSourceToAppResponse) GetDatasource() *Datasource { @@ -3826,7 +4077,7 @@ type DatasourceAccountLink struct { func (x *DatasourceAccountLink) Reset() { *x = DatasourceAccountLink{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[36] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[39] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3838,7 +4089,7 @@ func (x *DatasourceAccountLink) String() string { func (*DatasourceAccountLink) ProtoMessage() {} func (x *DatasourceAccountLink) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[36] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[39] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3851,7 +4102,7 @@ func (x *DatasourceAccountLink) ProtoReflect() protoreflect.Message { // Deprecated: Use DatasourceAccountLink.ProtoReflect.Descriptor instead. func (*DatasourceAccountLink) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{36} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{39} } func (x *DatasourceAccountLink) GetId() string { @@ -3921,7 +4172,7 @@ type DatasourceGroupBinding struct { func (x *DatasourceGroupBinding) Reset() { *x = DatasourceGroupBinding{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[37] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[40] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3933,7 +4184,7 @@ func (x *DatasourceGroupBinding) String() string { func (*DatasourceGroupBinding) ProtoMessage() {} func (x *DatasourceGroupBinding) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[37] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[40] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -3946,7 +4197,7 @@ func (x *DatasourceGroupBinding) ProtoReflect() protoreflect.Message { // Deprecated: Use DatasourceGroupBinding.ProtoReflect.Descriptor instead. func (*DatasourceGroupBinding) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{37} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{40} } func (x *DatasourceGroupBinding) GetId() string { @@ -4021,7 +4272,7 @@ type DatasourceVerifiedDomain struct { func (x *DatasourceVerifiedDomain) Reset() { *x = DatasourceVerifiedDomain{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[38] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[41] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4033,7 +4284,7 @@ func (x *DatasourceVerifiedDomain) String() string { func (*DatasourceVerifiedDomain) ProtoMessage() {} func (x *DatasourceVerifiedDomain) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[38] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[41] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4046,7 +4297,7 @@ func (x *DatasourceVerifiedDomain) ProtoReflect() protoreflect.Message { // Deprecated: Use DatasourceVerifiedDomain.ProtoReflect.Descriptor instead. func (*DatasourceVerifiedDomain) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{38} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{41} } func (x *DatasourceVerifiedDomain) GetId() string { @@ -4116,7 +4367,7 @@ type DatasourceDirectoryTeam struct { func (x *DatasourceDirectoryTeam) Reset() { *x = DatasourceDirectoryTeam{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[39] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[42] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4128,7 +4379,7 @@ func (x *DatasourceDirectoryTeam) String() string { func (*DatasourceDirectoryTeam) ProtoMessage() {} func (x *DatasourceDirectoryTeam) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[39] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[42] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4141,7 +4392,7 @@ func (x *DatasourceDirectoryTeam) ProtoReflect() protoreflect.Message { // Deprecated: Use DatasourceDirectoryTeam.ProtoReflect.Descriptor instead. func (*DatasourceDirectoryTeam) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{39} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{42} } func (x *DatasourceDirectoryTeam) GetId() string { @@ -4173,7 +4424,7 @@ type BeginDatasourceAccountLinkRequest struct { func (x *BeginDatasourceAccountLinkRequest) Reset() { *x = BeginDatasourceAccountLinkRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[40] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[43] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4185,7 +4436,7 @@ func (x *BeginDatasourceAccountLinkRequest) String() string { func (*BeginDatasourceAccountLinkRequest) ProtoMessage() {} func (x *BeginDatasourceAccountLinkRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[40] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[43] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4198,7 +4449,7 @@ func (x *BeginDatasourceAccountLinkRequest) ProtoReflect() protoreflect.Message // Deprecated: Use BeginDatasourceAccountLinkRequest.ProtoReflect.Descriptor instead. func (*BeginDatasourceAccountLinkRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{40} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{43} } func (x *BeginDatasourceAccountLinkRequest) GetOrgId() string { @@ -4243,7 +4494,7 @@ type BeginDatasourceAccountLinkResponse struct { func (x *BeginDatasourceAccountLinkResponse) Reset() { *x = BeginDatasourceAccountLinkResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[41] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[44] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4255,7 +4506,7 @@ func (x *BeginDatasourceAccountLinkResponse) String() string { func (*BeginDatasourceAccountLinkResponse) ProtoMessage() {} func (x *BeginDatasourceAccountLinkResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[41] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[44] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4268,7 +4519,7 @@ func (x *BeginDatasourceAccountLinkResponse) ProtoReflect() protoreflect.Message // Deprecated: Use BeginDatasourceAccountLinkResponse.ProtoReflect.Descriptor instead. func (*BeginDatasourceAccountLinkResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{41} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{44} } func (x *BeginDatasourceAccountLinkResponse) GetAuthorizeUrl() string { @@ -4303,7 +4554,7 @@ type CompleteDatasourceAccountLinkRequest struct { func (x *CompleteDatasourceAccountLinkRequest) Reset() { *x = CompleteDatasourceAccountLinkRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[42] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[45] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4315,7 +4566,7 @@ func (x *CompleteDatasourceAccountLinkRequest) String() string { func (*CompleteDatasourceAccountLinkRequest) ProtoMessage() {} func (x *CompleteDatasourceAccountLinkRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[42] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[45] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4328,7 +4579,7 @@ func (x *CompleteDatasourceAccountLinkRequest) ProtoReflect() protoreflect.Messa // Deprecated: Use CompleteDatasourceAccountLinkRequest.ProtoReflect.Descriptor instead. func (*CompleteDatasourceAccountLinkRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{42} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{45} } func (x *CompleteDatasourceAccountLinkRequest) GetOrgId() string { @@ -4361,7 +4612,7 @@ type CompleteDatasourceAccountLinkResponse struct { func (x *CompleteDatasourceAccountLinkResponse) Reset() { *x = CompleteDatasourceAccountLinkResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[43] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[46] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4373,7 +4624,7 @@ func (x *CompleteDatasourceAccountLinkResponse) String() string { func (*CompleteDatasourceAccountLinkResponse) ProtoMessage() {} func (x *CompleteDatasourceAccountLinkResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[43] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[46] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4386,7 +4637,7 @@ func (x *CompleteDatasourceAccountLinkResponse) ProtoReflect() protoreflect.Mess // Deprecated: Use CompleteDatasourceAccountLinkResponse.ProtoReflect.Descriptor instead. func (*CompleteDatasourceAccountLinkResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{43} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{46} } func (x *CompleteDatasourceAccountLinkResponse) GetLink() *DatasourceAccountLink { @@ -4405,7 +4656,7 @@ type ListMyDatasourceAccountLinksRequest struct { func (x *ListMyDatasourceAccountLinksRequest) Reset() { *x = ListMyDatasourceAccountLinksRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[44] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[47] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4417,7 +4668,7 @@ func (x *ListMyDatasourceAccountLinksRequest) String() string { func (*ListMyDatasourceAccountLinksRequest) ProtoMessage() {} func (x *ListMyDatasourceAccountLinksRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[44] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[47] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4430,7 +4681,7 @@ func (x *ListMyDatasourceAccountLinksRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use ListMyDatasourceAccountLinksRequest.ProtoReflect.Descriptor instead. func (*ListMyDatasourceAccountLinksRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{44} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{47} } func (x *ListMyDatasourceAccountLinksRequest) GetOrgId() string { @@ -4449,7 +4700,7 @@ type ListMyDatasourceAccountLinksResponse struct { func (x *ListMyDatasourceAccountLinksResponse) Reset() { *x = ListMyDatasourceAccountLinksResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[45] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[48] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4461,7 +4712,7 @@ func (x *ListMyDatasourceAccountLinksResponse) String() string { func (*ListMyDatasourceAccountLinksResponse) ProtoMessage() {} func (x *ListMyDatasourceAccountLinksResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[45] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[48] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4474,7 +4725,7 @@ func (x *ListMyDatasourceAccountLinksResponse) ProtoReflect() protoreflect.Messa // Deprecated: Use ListMyDatasourceAccountLinksResponse.ProtoReflect.Descriptor instead. func (*ListMyDatasourceAccountLinksResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{45} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{48} } func (x *ListMyDatasourceAccountLinksResponse) GetLinks() []*DatasourceAccountLink { @@ -4494,7 +4745,7 @@ type DeleteDatasourceAccountLinkRequest struct { func (x *DeleteDatasourceAccountLinkRequest) Reset() { *x = DeleteDatasourceAccountLinkRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[46] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[49] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4506,7 +4757,7 @@ func (x *DeleteDatasourceAccountLinkRequest) String() string { func (*DeleteDatasourceAccountLinkRequest) ProtoMessage() {} func (x *DeleteDatasourceAccountLinkRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[46] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[49] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4519,7 +4770,7 @@ func (x *DeleteDatasourceAccountLinkRequest) ProtoReflect() protoreflect.Message // Deprecated: Use DeleteDatasourceAccountLinkRequest.ProtoReflect.Descriptor instead. func (*DeleteDatasourceAccountLinkRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{46} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{49} } func (x *DeleteDatasourceAccountLinkRequest) GetOrgId() string { @@ -4544,7 +4795,7 @@ type DeleteDatasourceAccountLinkResponse struct { func (x *DeleteDatasourceAccountLinkResponse) Reset() { *x = DeleteDatasourceAccountLinkResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[47] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[50] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4556,7 +4807,7 @@ func (x *DeleteDatasourceAccountLinkResponse) String() string { func (*DeleteDatasourceAccountLinkResponse) ProtoMessage() {} func (x *DeleteDatasourceAccountLinkResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[47] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[50] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4569,7 +4820,7 @@ func (x *DeleteDatasourceAccountLinkResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use DeleteDatasourceAccountLinkResponse.ProtoReflect.Descriptor instead. func (*DeleteDatasourceAccountLinkResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{47} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{50} } type GetDatasourceDirectoryRequest struct { @@ -4581,7 +4832,7 @@ type GetDatasourceDirectoryRequest struct { func (x *GetDatasourceDirectoryRequest) Reset() { *x = GetDatasourceDirectoryRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[48] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[51] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4593,7 +4844,7 @@ func (x *GetDatasourceDirectoryRequest) String() string { func (*GetDatasourceDirectoryRequest) ProtoMessage() {} func (x *GetDatasourceDirectoryRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[48] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[51] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4606,7 +4857,7 @@ func (x *GetDatasourceDirectoryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDatasourceDirectoryRequest.ProtoReflect.Descriptor instead. func (*GetDatasourceDirectoryRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{48} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{51} } func (x *GetDatasourceDirectoryRequest) GetOrgId() string { @@ -4629,7 +4880,7 @@ type GetDatasourceDirectoryResponse struct { func (x *GetDatasourceDirectoryResponse) Reset() { *x = GetDatasourceDirectoryResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[49] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[52] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4641,7 +4892,7 @@ func (x *GetDatasourceDirectoryResponse) String() string { func (*GetDatasourceDirectoryResponse) ProtoMessage() {} func (x *GetDatasourceDirectoryResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[49] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[52] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4654,7 +4905,7 @@ func (x *GetDatasourceDirectoryResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use GetDatasourceDirectoryResponse.ProtoReflect.Descriptor instead. func (*GetDatasourceDirectoryResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{49} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{52} } func (x *GetDatasourceDirectoryResponse) GetLinks() []*DatasourceAccountLink { @@ -4697,7 +4948,7 @@ type BindDatasourceGroupRequest struct { func (x *BindDatasourceGroupRequest) Reset() { *x = BindDatasourceGroupRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[50] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[53] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4709,7 +4960,7 @@ func (x *BindDatasourceGroupRequest) String() string { func (*BindDatasourceGroupRequest) ProtoMessage() {} func (x *BindDatasourceGroupRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[50] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[53] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4722,7 +4973,7 @@ func (x *BindDatasourceGroupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use BindDatasourceGroupRequest.ProtoReflect.Descriptor instead. func (*BindDatasourceGroupRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{50} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{53} } func (x *BindDatasourceGroupRequest) GetOrgId() string { @@ -4762,7 +5013,7 @@ type BindDatasourceGroupResponse struct { func (x *BindDatasourceGroupResponse) Reset() { *x = BindDatasourceGroupResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[51] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[54] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4774,7 +5025,7 @@ func (x *BindDatasourceGroupResponse) String() string { func (*BindDatasourceGroupResponse) ProtoMessage() {} func (x *BindDatasourceGroupResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[51] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[54] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4787,7 +5038,7 @@ func (x *BindDatasourceGroupResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use BindDatasourceGroupResponse.ProtoReflect.Descriptor instead. func (*BindDatasourceGroupResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{51} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{54} } func (x *BindDatasourceGroupResponse) GetBinding() *DatasourceGroupBinding { @@ -4807,7 +5058,7 @@ type UnbindDatasourceGroupRequest struct { func (x *UnbindDatasourceGroupRequest) Reset() { *x = UnbindDatasourceGroupRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[52] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[55] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4819,7 +5070,7 @@ func (x *UnbindDatasourceGroupRequest) String() string { func (*UnbindDatasourceGroupRequest) ProtoMessage() {} func (x *UnbindDatasourceGroupRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[52] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[55] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4832,7 +5083,7 @@ func (x *UnbindDatasourceGroupRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use UnbindDatasourceGroupRequest.ProtoReflect.Descriptor instead. func (*UnbindDatasourceGroupRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{52} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{55} } func (x *UnbindDatasourceGroupRequest) GetOrgId() string { @@ -4857,7 +5108,7 @@ type UnbindDatasourceGroupResponse struct { func (x *UnbindDatasourceGroupResponse) Reset() { *x = UnbindDatasourceGroupResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[53] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[56] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4869,7 +5120,7 @@ func (x *UnbindDatasourceGroupResponse) String() string { func (*UnbindDatasourceGroupResponse) ProtoMessage() {} func (x *UnbindDatasourceGroupResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[53] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[56] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4882,7 +5133,7 @@ func (x *UnbindDatasourceGroupResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use UnbindDatasourceGroupResponse.ProtoReflect.Descriptor instead. func (*UnbindDatasourceGroupResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{53} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{56} } type ClaimDatasourceDomainRequest struct { @@ -4895,7 +5146,7 @@ type ClaimDatasourceDomainRequest struct { func (x *ClaimDatasourceDomainRequest) Reset() { *x = ClaimDatasourceDomainRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[54] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[57] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4907,7 +5158,7 @@ func (x *ClaimDatasourceDomainRequest) String() string { func (*ClaimDatasourceDomainRequest) ProtoMessage() {} func (x *ClaimDatasourceDomainRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[54] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[57] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4920,7 +5171,7 @@ func (x *ClaimDatasourceDomainRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ClaimDatasourceDomainRequest.ProtoReflect.Descriptor instead. func (*ClaimDatasourceDomainRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{54} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{57} } func (x *ClaimDatasourceDomainRequest) GetOrgId() string { @@ -4946,7 +5197,7 @@ type ClaimDatasourceDomainResponse struct { func (x *ClaimDatasourceDomainResponse) Reset() { *x = ClaimDatasourceDomainResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[55] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[58] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4958,7 +5209,7 @@ func (x *ClaimDatasourceDomainResponse) String() string { func (*ClaimDatasourceDomainResponse) ProtoMessage() {} func (x *ClaimDatasourceDomainResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[55] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[58] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4971,7 +5222,7 @@ func (x *ClaimDatasourceDomainResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ClaimDatasourceDomainResponse.ProtoReflect.Descriptor instead. func (*ClaimDatasourceDomainResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{55} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{58} } func (x *ClaimDatasourceDomainResponse) GetDomain() *DatasourceVerifiedDomain { @@ -4991,7 +5242,7 @@ type VerifyDatasourceDomainRequest struct { func (x *VerifyDatasourceDomainRequest) Reset() { *x = VerifyDatasourceDomainRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[56] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[59] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5003,7 +5254,7 @@ func (x *VerifyDatasourceDomainRequest) String() string { func (*VerifyDatasourceDomainRequest) ProtoMessage() {} func (x *VerifyDatasourceDomainRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[56] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[59] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5016,7 +5267,7 @@ func (x *VerifyDatasourceDomainRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyDatasourceDomainRequest.ProtoReflect.Descriptor instead. func (*VerifyDatasourceDomainRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{56} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{59} } func (x *VerifyDatasourceDomainRequest) GetOrgId() string { @@ -5042,7 +5293,7 @@ type VerifyDatasourceDomainResponse struct { func (x *VerifyDatasourceDomainResponse) Reset() { *x = VerifyDatasourceDomainResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[57] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[60] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5054,7 +5305,7 @@ func (x *VerifyDatasourceDomainResponse) String() string { func (*VerifyDatasourceDomainResponse) ProtoMessage() {} func (x *VerifyDatasourceDomainResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[57] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[60] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5067,7 +5318,7 @@ func (x *VerifyDatasourceDomainResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VerifyDatasourceDomainResponse.ProtoReflect.Descriptor instead. func (*VerifyDatasourceDomainResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{57} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{60} } func (x *VerifyDatasourceDomainResponse) GetDomain() *DatasourceVerifiedDomain { @@ -5087,7 +5338,7 @@ type DeleteDatasourceDomainRequest struct { func (x *DeleteDatasourceDomainRequest) Reset() { *x = DeleteDatasourceDomainRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[58] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[61] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5099,7 +5350,7 @@ func (x *DeleteDatasourceDomainRequest) String() string { func (*DeleteDatasourceDomainRequest) ProtoMessage() {} func (x *DeleteDatasourceDomainRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[58] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[61] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5112,7 +5363,7 @@ func (x *DeleteDatasourceDomainRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteDatasourceDomainRequest.ProtoReflect.Descriptor instead. func (*DeleteDatasourceDomainRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{58} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{61} } func (x *DeleteDatasourceDomainRequest) GetOrgId() string { @@ -5137,7 +5388,7 @@ type DeleteDatasourceDomainResponse struct { func (x *DeleteDatasourceDomainResponse) Reset() { *x = DeleteDatasourceDomainResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[59] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[62] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5149,7 +5400,7 @@ func (x *DeleteDatasourceDomainResponse) String() string { func (*DeleteDatasourceDomainResponse) ProtoMessage() {} func (x *DeleteDatasourceDomainResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[59] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[62] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5162,7 +5413,7 @@ func (x *DeleteDatasourceDomainResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeleteDatasourceDomainResponse.ProtoReflect.Descriptor instead. func (*DeleteDatasourceDomainResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{59} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{62} } // SourceDelegation records that a person, by connecting a source, delegated @@ -5192,7 +5443,7 @@ type SourceDelegation struct { func (x *SourceDelegation) Reset() { *x = SourceDelegation{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[60] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[63] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5204,7 +5455,7 @@ func (x *SourceDelegation) String() string { func (*SourceDelegation) ProtoMessage() {} func (x *SourceDelegation) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[60] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[63] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5217,7 +5468,7 @@ func (x *SourceDelegation) ProtoReflect() protoreflect.Message { // Deprecated: Use SourceDelegation.ProtoReflect.Descriptor instead. func (*SourceDelegation) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{60} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{63} } func (x *SourceDelegation) GetId() string { @@ -5296,7 +5547,7 @@ type ListSourceDelegationsRequest struct { func (x *ListSourceDelegationsRequest) Reset() { *x = ListSourceDelegationsRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[61] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[64] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5308,7 +5559,7 @@ func (x *ListSourceDelegationsRequest) String() string { func (*ListSourceDelegationsRequest) ProtoMessage() {} func (x *ListSourceDelegationsRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[61] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[64] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5321,7 +5572,7 @@ func (x *ListSourceDelegationsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSourceDelegationsRequest.ProtoReflect.Descriptor instead. func (*ListSourceDelegationsRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{61} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{64} } func (x *ListSourceDelegationsRequest) GetOrgId() string { @@ -5355,7 +5606,7 @@ type ListSourceDelegationsResponse struct { func (x *ListSourceDelegationsResponse) Reset() { *x = ListSourceDelegationsResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[62] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[65] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5367,7 +5618,7 @@ func (x *ListSourceDelegationsResponse) String() string { func (*ListSourceDelegationsResponse) ProtoMessage() {} func (x *ListSourceDelegationsResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[62] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[65] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5380,7 +5631,7 @@ func (x *ListSourceDelegationsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSourceDelegationsResponse.ProtoReflect.Descriptor instead. func (*ListSourceDelegationsResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{62} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{65} } func (x *ListSourceDelegationsResponse) GetDelegations() []*SourceDelegation { @@ -5400,7 +5651,7 @@ type RevokeSourceDelegationRequest struct { func (x *RevokeSourceDelegationRequest) Reset() { *x = RevokeSourceDelegationRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[63] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[66] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5412,7 +5663,7 @@ func (x *RevokeSourceDelegationRequest) String() string { func (*RevokeSourceDelegationRequest) ProtoMessage() {} func (x *RevokeSourceDelegationRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[63] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[66] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5425,7 +5676,7 @@ func (x *RevokeSourceDelegationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RevokeSourceDelegationRequest.ProtoReflect.Descriptor instead. func (*RevokeSourceDelegationRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{63} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{66} } func (x *RevokeSourceDelegationRequest) GetOrgId() string { @@ -5451,7 +5702,7 @@ type RevokeSourceDelegationResponse struct { func (x *RevokeSourceDelegationResponse) Reset() { *x = RevokeSourceDelegationResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[64] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[67] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5463,7 +5714,7 @@ func (x *RevokeSourceDelegationResponse) String() string { func (*RevokeSourceDelegationResponse) ProtoMessage() {} func (x *RevokeSourceDelegationResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[64] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[67] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5476,7 +5727,7 @@ func (x *RevokeSourceDelegationResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RevokeSourceDelegationResponse.ProtoReflect.Descriptor instead. func (*RevokeSourceDelegationResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{64} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{67} } func (x *RevokeSourceDelegationResponse) GetDelegation() *SourceDelegation { @@ -5534,7 +5785,7 @@ type TriggerIdentity struct { func (x *TriggerIdentity) Reset() { *x = TriggerIdentity{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[65] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[68] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5546,7 +5797,7 @@ func (x *TriggerIdentity) String() string { func (*TriggerIdentity) ProtoMessage() {} func (x *TriggerIdentity) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[65] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[68] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5559,7 +5810,7 @@ func (x *TriggerIdentity) ProtoReflect() protoreflect.Message { // Deprecated: Use TriggerIdentity.ProtoReflect.Descriptor instead. func (*TriggerIdentity) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{65} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{68} } func (x *TriggerIdentity) GetSource() string { @@ -5611,7 +5862,7 @@ type DeclaredTriggerSchedule struct { func (x *DeclaredTriggerSchedule) Reset() { *x = DeclaredTriggerSchedule{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[66] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[69] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5623,7 +5874,7 @@ func (x *DeclaredTriggerSchedule) String() string { func (*DeclaredTriggerSchedule) ProtoMessage() {} func (x *DeclaredTriggerSchedule) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[66] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[69] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5636,7 +5887,7 @@ func (x *DeclaredTriggerSchedule) ProtoReflect() protoreflect.Message { // Deprecated: Use DeclaredTriggerSchedule.ProtoReflect.Descriptor instead. func (*DeclaredTriggerSchedule) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{66} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{69} } func (x *DeclaredTriggerSchedule) GetCron() string { @@ -5721,7 +5972,7 @@ type TriggerDelegationReference struct { func (x *TriggerDelegationReference) Reset() { *x = TriggerDelegationReference{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[67] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[70] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5733,7 +5984,7 @@ func (x *TriggerDelegationReference) String() string { func (*TriggerDelegationReference) ProtoMessage() {} func (x *TriggerDelegationReference) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[67] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[70] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5746,7 +5997,7 @@ func (x *TriggerDelegationReference) ProtoReflect() protoreflect.Message { // Deprecated: Use TriggerDelegationReference.ProtoReflect.Descriptor instead. func (*TriggerDelegationReference) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{67} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{70} } func (x *TriggerDelegationReference) GetReference() string { @@ -5858,7 +6109,7 @@ type TriggerChildContext struct { func (x *TriggerChildContext) Reset() { *x = TriggerChildContext{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[68] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[71] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5870,7 +6121,7 @@ func (x *TriggerChildContext) String() string { func (*TriggerChildContext) ProtoMessage() {} func (x *TriggerChildContext) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[68] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[71] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5883,7 +6134,7 @@ func (x *TriggerChildContext) ProtoReflect() protoreflect.Message { // Deprecated: Use TriggerChildContext.ProtoReflect.Descriptor instead. func (*TriggerChildContext) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{68} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{71} } func (x *TriggerChildContext) GetDelegationId() string { @@ -5975,7 +6226,7 @@ type SourceTriggerDeclaration struct { func (x *SourceTriggerDeclaration) Reset() { *x = SourceTriggerDeclaration{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[69] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[72] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5987,7 +6238,7 @@ func (x *SourceTriggerDeclaration) String() string { func (*SourceTriggerDeclaration) ProtoMessage() {} func (x *SourceTriggerDeclaration) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[69] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[72] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6000,7 +6251,7 @@ func (x *SourceTriggerDeclaration) ProtoReflect() protoreflect.Message { // Deprecated: Use SourceTriggerDeclaration.ProtoReflect.Descriptor instead. func (*SourceTriggerDeclaration) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{69} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{72} } func (x *SourceTriggerDeclaration) GetRelease() string { @@ -6069,7 +6320,7 @@ type DelegateSourceTriggersRequest struct { func (x *DelegateSourceTriggersRequest) Reset() { *x = DelegateSourceTriggersRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[70] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[73] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6081,7 +6332,7 @@ func (x *DelegateSourceTriggersRequest) String() string { func (*DelegateSourceTriggersRequest) ProtoMessage() {} func (x *DelegateSourceTriggersRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[70] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[73] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6094,7 +6345,7 @@ func (x *DelegateSourceTriggersRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DelegateSourceTriggersRequest.ProtoReflect.Descriptor instead. func (*DelegateSourceTriggersRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{70} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{73} } func (x *DelegateSourceTriggersRequest) GetOrgId() string { @@ -6123,7 +6374,7 @@ type DelegateSourceTriggersResponse struct { func (x *DelegateSourceTriggersResponse) Reset() { *x = DelegateSourceTriggersResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[71] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[74] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6135,7 +6386,7 @@ func (x *DelegateSourceTriggersResponse) String() string { func (*DelegateSourceTriggersResponse) ProtoMessage() {} func (x *DelegateSourceTriggersResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[71] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[74] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6148,7 +6399,7 @@ func (x *DelegateSourceTriggersResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DelegateSourceTriggersResponse.ProtoReflect.Descriptor instead. func (*DelegateSourceTriggersResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{71} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{74} } func (x *DelegateSourceTriggersResponse) GetDelegations() []*TriggerDelegationReference { @@ -6168,7 +6419,7 @@ type GetSourceTriggerDeclarationRequest struct { func (x *GetSourceTriggerDeclarationRequest) Reset() { *x = GetSourceTriggerDeclarationRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[72] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[75] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6180,7 +6431,7 @@ func (x *GetSourceTriggerDeclarationRequest) String() string { func (*GetSourceTriggerDeclarationRequest) ProtoMessage() {} func (x *GetSourceTriggerDeclarationRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[72] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[75] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6193,7 +6444,7 @@ func (x *GetSourceTriggerDeclarationRequest) ProtoReflect() protoreflect.Message // Deprecated: Use GetSourceTriggerDeclarationRequest.ProtoReflect.Descriptor instead. func (*GetSourceTriggerDeclarationRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{72} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{75} } func (x *GetSourceTriggerDeclarationRequest) GetOrgId() string { @@ -6225,7 +6476,7 @@ type GetSourceTriggerDeclarationResponse struct { func (x *GetSourceTriggerDeclarationResponse) Reset() { *x = GetSourceTriggerDeclarationResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[73] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[76] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6237,7 +6488,7 @@ func (x *GetSourceTriggerDeclarationResponse) String() string { func (*GetSourceTriggerDeclarationResponse) ProtoMessage() {} func (x *GetSourceTriggerDeclarationResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[73] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[76] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6250,7 +6501,7 @@ func (x *GetSourceTriggerDeclarationResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use GetSourceTriggerDeclarationResponse.ProtoReflect.Descriptor instead. func (*GetSourceTriggerDeclarationResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{73} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{76} } func (x *GetSourceTriggerDeclarationResponse) GetDeclaration() *SourceTriggerDeclaration { @@ -6287,7 +6538,7 @@ type ReconcileSourceRequest struct { func (x *ReconcileSourceRequest) Reset() { *x = ReconcileSourceRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[74] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[77] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6299,7 +6550,7 @@ func (x *ReconcileSourceRequest) String() string { func (*ReconcileSourceRequest) ProtoMessage() {} func (x *ReconcileSourceRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[74] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[77] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6312,7 +6563,7 @@ func (x *ReconcileSourceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ReconcileSourceRequest.ProtoReflect.Descriptor instead. func (*ReconcileSourceRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{74} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{77} } func (x *ReconcileSourceRequest) GetOrgId() string { @@ -6352,7 +6603,7 @@ type ReconcileSourceResponse struct { func (x *ReconcileSourceResponse) Reset() { *x = ReconcileSourceResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[75] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[78] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6364,7 +6615,7 @@ func (x *ReconcileSourceResponse) String() string { func (*ReconcileSourceResponse) ProtoMessage() {} func (x *ReconcileSourceResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[75] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[78] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6377,7 +6628,7 @@ func (x *ReconcileSourceResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ReconcileSourceResponse.ProtoReflect.Descriptor instead. func (*ReconcileSourceResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{75} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{78} } func (x *ReconcileSourceResponse) GetEffectId() string { @@ -6410,7 +6661,7 @@ type LookupReconcileSourceRequest struct { func (x *LookupReconcileSourceRequest) Reset() { *x = LookupReconcileSourceRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[76] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[79] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6422,7 +6673,7 @@ func (x *LookupReconcileSourceRequest) String() string { func (*LookupReconcileSourceRequest) ProtoMessage() {} func (x *LookupReconcileSourceRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[76] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[79] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6435,7 +6686,7 @@ func (x *LookupReconcileSourceRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use LookupReconcileSourceRequest.ProtoReflect.Descriptor instead. func (*LookupReconcileSourceRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{76} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{79} } func (x *LookupReconcileSourceRequest) GetEffectId() string { @@ -6466,7 +6717,7 @@ type SourceOperation struct { func (x *SourceOperation) Reset() { *x = SourceOperation{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[77] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[80] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6478,7 +6729,7 @@ func (x *SourceOperation) String() string { func (*SourceOperation) ProtoMessage() {} func (x *SourceOperation) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[77] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[80] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6491,7 +6742,7 @@ func (x *SourceOperation) ProtoReflect() protoreflect.Message { // Deprecated: Use SourceOperation.ProtoReflect.Descriptor instead. func (*SourceOperation) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{77} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{80} } func (x *SourceOperation) GetName() string { @@ -6575,7 +6826,7 @@ type DeclareSourceOperationsRequest struct { func (x *DeclareSourceOperationsRequest) Reset() { *x = DeclareSourceOperationsRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[78] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[81] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6587,7 +6838,7 @@ func (x *DeclareSourceOperationsRequest) String() string { func (*DeclareSourceOperationsRequest) ProtoMessage() {} func (x *DeclareSourceOperationsRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[78] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[81] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6600,7 +6851,7 @@ func (x *DeclareSourceOperationsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use DeclareSourceOperationsRequest.ProtoReflect.Descriptor instead. func (*DeclareSourceOperationsRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{78} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{81} } func (x *DeclareSourceOperationsRequest) GetOrgId() string { @@ -6633,7 +6884,7 @@ type DeclareSourceOperationsResponse struct { func (x *DeclareSourceOperationsResponse) Reset() { *x = DeclareSourceOperationsResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[79] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[82] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6645,7 +6896,7 @@ func (x *DeclareSourceOperationsResponse) String() string { func (*DeclareSourceOperationsResponse) ProtoMessage() {} func (x *DeclareSourceOperationsResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[79] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[82] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6658,7 +6909,7 @@ func (x *DeclareSourceOperationsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use DeclareSourceOperationsResponse.ProtoReflect.Descriptor instead. func (*DeclareSourceOperationsResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{79} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{82} } func (x *DeclareSourceOperationsResponse) GetOperations() []*SourceOperation { @@ -6678,7 +6929,7 @@ type ListSourceOperationsRequest struct { func (x *ListSourceOperationsRequest) Reset() { *x = ListSourceOperationsRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[80] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[83] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6690,7 +6941,7 @@ func (x *ListSourceOperationsRequest) String() string { func (*ListSourceOperationsRequest) ProtoMessage() {} func (x *ListSourceOperationsRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[80] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[83] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6703,7 +6954,7 @@ func (x *ListSourceOperationsRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSourceOperationsRequest.ProtoReflect.Descriptor instead. func (*ListSourceOperationsRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{80} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{83} } func (x *ListSourceOperationsRequest) GetOrgId() string { @@ -6729,7 +6980,7 @@ type ListSourceOperationsResponse struct { func (x *ListSourceOperationsResponse) Reset() { *x = ListSourceOperationsResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[81] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[84] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6741,7 +6992,7 @@ func (x *ListSourceOperationsResponse) String() string { func (*ListSourceOperationsResponse) ProtoMessage() {} func (x *ListSourceOperationsResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[81] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[84] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6754,7 +7005,7 @@ func (x *ListSourceOperationsResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ListSourceOperationsResponse.ProtoReflect.Descriptor instead. func (*ListSourceOperationsResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{81} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{84} } func (x *ListSourceOperationsResponse) GetOperations() []*SourceOperation { @@ -6779,7 +7030,7 @@ type InvokeSourceOperationRequest struct { func (x *InvokeSourceOperationRequest) Reset() { *x = InvokeSourceOperationRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[82] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[85] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6791,7 +7042,7 @@ func (x *InvokeSourceOperationRequest) String() string { func (*InvokeSourceOperationRequest) ProtoMessage() {} func (x *InvokeSourceOperationRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[82] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[85] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6804,7 +7055,7 @@ func (x *InvokeSourceOperationRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use InvokeSourceOperationRequest.ProtoReflect.Descriptor instead. func (*InvokeSourceOperationRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{82} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{85} } func (x *InvokeSourceOperationRequest) GetOrgId() string { @@ -6856,7 +7107,7 @@ type SourceOperationReceipt struct { func (x *SourceOperationReceipt) Reset() { *x = SourceOperationReceipt{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[83] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[86] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6868,7 +7119,7 @@ func (x *SourceOperationReceipt) String() string { func (*SourceOperationReceipt) ProtoMessage() {} func (x *SourceOperationReceipt) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[83] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[86] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6881,7 +7132,7 @@ func (x *SourceOperationReceipt) ProtoReflect() protoreflect.Message { // Deprecated: Use SourceOperationReceipt.ProtoReflect.Descriptor instead. func (*SourceOperationReceipt) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{83} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{86} } func (x *SourceOperationReceipt) GetEffectId() string { @@ -6929,7 +7180,7 @@ type InvokeSourceOperationResponse struct { func (x *InvokeSourceOperationResponse) Reset() { *x = InvokeSourceOperationResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[84] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[87] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6941,7 +7192,7 @@ func (x *InvokeSourceOperationResponse) String() string { func (*InvokeSourceOperationResponse) ProtoMessage() {} func (x *InvokeSourceOperationResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[84] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[87] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6954,7 +7205,7 @@ func (x *InvokeSourceOperationResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use InvokeSourceOperationResponse.ProtoReflect.Descriptor instead. func (*InvokeSourceOperationResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{84} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{87} } func (x *InvokeSourceOperationResponse) GetOutputJson() string { @@ -6982,7 +7233,7 @@ type LookupInvokeSourceOperationRequest struct { func (x *LookupInvokeSourceOperationRequest) Reset() { *x = LookupInvokeSourceOperationRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[85] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6994,7 +7245,7 @@ func (x *LookupInvokeSourceOperationRequest) String() string { func (*LookupInvokeSourceOperationRequest) ProtoMessage() {} func (x *LookupInvokeSourceOperationRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[85] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7007,7 +7258,7 @@ func (x *LookupInvokeSourceOperationRequest) ProtoReflect() protoreflect.Message // Deprecated: Use LookupInvokeSourceOperationRequest.ProtoReflect.Descriptor instead. func (*LookupInvokeSourceOperationRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{85} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{88} } func (x *LookupInvokeSourceOperationRequest) GetEffectId() string { @@ -7029,7 +7280,7 @@ type PruneSourceOperationReceiptsRequest struct { func (x *PruneSourceOperationReceiptsRequest) Reset() { *x = PruneSourceOperationReceiptsRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[86] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7041,7 +7292,7 @@ func (x *PruneSourceOperationReceiptsRequest) String() string { func (*PruneSourceOperationReceiptsRequest) ProtoMessage() {} func (x *PruneSourceOperationReceiptsRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[86] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7054,7 +7305,7 @@ func (x *PruneSourceOperationReceiptsRequest) ProtoReflect() protoreflect.Messag // Deprecated: Use PruneSourceOperationReceiptsRequest.ProtoReflect.Descriptor instead. func (*PruneSourceOperationReceiptsRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{86} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{89} } func (x *PruneSourceOperationReceiptsRequest) GetOrgId() string { @@ -7089,7 +7340,7 @@ type PruneSourceOperationReceiptsResponse struct { func (x *PruneSourceOperationReceiptsResponse) Reset() { *x = PruneSourceOperationReceiptsResponse{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[87] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7101,7 +7352,7 @@ func (x *PruneSourceOperationReceiptsResponse) String() string { func (*PruneSourceOperationReceiptsResponse) ProtoMessage() {} func (x *PruneSourceOperationReceiptsResponse) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[87] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7114,7 +7365,7 @@ func (x *PruneSourceOperationReceiptsResponse) ProtoReflect() protoreflect.Messa // Deprecated: Use PruneSourceOperationReceiptsResponse.ProtoReflect.Descriptor instead. func (*PruneSourceOperationReceiptsResponse) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{87} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{90} } func (x *PruneSourceOperationReceiptsResponse) GetEffectId() string { @@ -7147,7 +7398,7 @@ type LookupPruneSourceOperationReceiptsRequest struct { func (x *LookupPruneSourceOperationReceiptsRequest) Reset() { *x = LookupPruneSourceOperationReceiptsRequest{} - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[88] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -7159,7 +7410,7 @@ func (x *LookupPruneSourceOperationReceiptsRequest) String() string { func (*LookupPruneSourceOperationReceiptsRequest) ProtoMessage() {} func (x *LookupPruneSourceOperationReceiptsRequest) ProtoReflect() protoreflect.Message { - mi := &file_saas_accounts_v1_datasource_proto_msgTypes[88] + mi := &file_saas_accounts_v1_datasource_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -7172,7 +7423,7 @@ func (x *LookupPruneSourceOperationReceiptsRequest) ProtoReflect() protoreflect. // Deprecated: Use LookupPruneSourceOperationReceiptsRequest.ProtoReflect.Descriptor instead. func (*LookupPruneSourceOperationReceiptsRequest) Descriptor() ([]byte, []int) { - return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{88} + return file_saas_accounts_v1_datasource_proto_rawDescGZIP(), []int{91} } func (x *LookupPruneSourceOperationReceiptsRequest) GetEffectId() string { @@ -7288,7 +7539,24 @@ const file_saas_accounts_v1_datasource_proto_rawDesc = "" + "\x11AddSourceResponse\x12<\n" + "\n" + "datasource\x18\x01 \x01(\v2\x1c.saas.accounts.v1.DatasourceR\n" + - "datasource\"|\n" + + "datasource\"\xbe\x02\n" + + "\x13UpdateSourceRequest\x12\x1f\n" + + "\x06org_id\x18\x01 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x05orgId\x12\x18\n" + + "\x02id\x18\x02 \x01(\tB\b\xbaH\x05r\x03\xb0\x01\x01R\x02id\x12 \n" + + "\x06branch\x18\x03 \x01(\tB\b\xbaH\x05r\x03\x18\xff\x01R\x06branch\x12'\n" + + "\x05paths\x18\x04 \x03(\tB\x11\xbaH\x0e\x92\x01\v\x10@\"\ar\x05\x10\x01\x18\x80\x04R\x05paths\x12Y\n" + + "\x0ffile_extensions\x18\x05 \x03(\tB0\xbaH-\x92\x01*\x10 \"&r$\x10\x02\x18!2\x1e^\\.[A-Za-z0-9][A-Za-z0-9._-]*$R\x0efileExtensions\x12F\n" + + "\bexpected\x18\x06 \x01(\v2\".saas.accounts.v1.SourceFetchScopeB\x06\xbaH\x03\xc8\x01\x01R\bexpected\"\x98\x01\n" + + "\x10SourceFetchScope\x12 \n" + + "\x06branch\x18\x01 \x01(\tB\b\xbaH\x05r\x03\x18\xff\x01R\x06branch\x12'\n" + + "\x05paths\x18\x02 \x03(\tB\x11\xbaH\x0e\x92\x01\v\x10@\"\ar\x05\x10\x01\x18\x80\x04R\x05paths\x129\n" + + "\x0ffile_extensions\x18\x03 \x03(\tB\x10\xbaH\r\x92\x01\n" + + "\x10 \"\x06r\x04\x10\x01\x18!R\x0efileExtensions\"k\n" + + "\x14UpdateSourceResponse\x12<\n" + + "\n" + + "datasource\x18\x01 \x01(\v2\x1c.saas.accounts.v1.DatasourceR\n" + + "datasource\x12\x15\n" + + "\x06job_id\x18\x02 \x01(\tR\x05jobId\"|\n" + "\x15DatasourceConfigField\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12!\n" + "\fdisplay_name\x18\x02 \x01(\tR\vdisplayName\x12\x12\n" + @@ -7742,12 +8010,13 @@ const file_saas_accounts_v1_datasource_proto_rawDesc = "" + "$SOURCE_SYNC_FAILURE_REASON_TOO_LARGE\x10\x05\x12/\n" + "+SOURCE_SYNC_FAILURE_REASON_HOST_UNAVAILABLE\x10\x06\x12$\n" + " SOURCE_SYNC_FAILURE_REASON_OTHER\x10\a\x12.\n" + - "*SOURCE_SYNC_FAILURE_REASON_DELIVERY_FAILED\x10\b*\x9c\x01\n" + + "*SOURCE_SYNC_FAILURE_REASON_DELIVERY_FAILED\x10\b*\xc2\x01\n" + "\x11SourceSyncTrigger\x12#\n" + "\x1fSOURCE_SYNC_TRIGGER_UNSPECIFIED\x10\x00\x12\x1e\n" + "\x1aSOURCE_SYNC_TRIGGER_MANUAL\x10\x01\x12!\n" + "\x1dSOURCE_SYNC_TRIGGER_SCHEDULED\x10\x02\x12\x1f\n" + - "\x1bSOURCE_SYNC_TRIGGER_WEBHOOK\x10\x03*\x8f\x01\n" + + "\x1bSOURCE_SYNC_TRIGGER_WEBHOOK\x10\x03\x12$\n" + + " SOURCE_SYNC_TRIGGER_RECONFIGURED\x10\x04*\x8f\x01\n" + "\x16DatasourceDomainStatus\x12(\n" + "$DATASOURCE_DOMAIN_STATUS_UNSPECIFIED\x10\x00\x12$\n" + " DATASOURCE_DOMAIN_STATUS_PENDING\x10\x01\x12%\n" + @@ -7760,7 +8029,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\x898\n" + + ",SOURCE_DELEGATION_REVOCATION_BINDING_CHANGED\x10\a2\xb19\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" + @@ -7786,7 +8055,10 @@ const file_saas_accounts_v1_datasource_proto_rawDesc = "" + "\x1csaas.datasource.source.added\x10\x02@\x01H\x04P\x04X\x03`\x01\x12\x9a\x01\n" + "\tAddSource\x12\".saas.accounts.v1.AddSourceRequest\x1a#.saas.accounts.v1.AddSourceResponse\"D\xc2\xf3\x18@\b\x02\x10\x04*\f\n" + "\x06org_id\x10\x02\x18\x010\x01: \n" + - "\x1csaas.datasource.source.added\x10\x02@\x01H\x04P\x04X\x03`\x01\x12\x8f\x01\n" + + "\x1csaas.datasource.source.added\x10\x02@\x01H\x04P\x04X\x03`\x01\x12\xa5\x01\n" + + "\fUpdateSource\x12%.saas.accounts.v1.UpdateSourceRequest\x1a&.saas.accounts.v1.UpdateSourceResponse\"F\xc2\xf3\x18B\b\x02\x10\x04*\f\n" + + "\x06org_id\x10\x02\x18\x010\x01:\"\n" + + "\x1esaas.datasource.source.updated\x10\x02@\x01H\x04P\x02X\x03`\x01\x12\x8f\x01\n" + "\x14GetDatasourceCatalog\x12-.saas.accounts.v1.GetDatasourceCatalogRequest\x1a..saas.accounts.v1.GetDatasourceCatalogResponse\"\x18\xc2\xf3\x18\x14\b\x02\x10\x010\x01:\x02\x10\x01@\x01H\x03P\x02X\x02`\x01\x12\x82\x01\n" + "\vListSources\x12$.saas.accounts.v1.ListSourcesRequest\x1a%.saas.accounts.v1.ListSourcesResponse\"&\xc2\xf3\x18\"\b\x02\x10\x03*\f\n" + "\x06org_id\x10\x02\x18\x010\x01:\x02\x10\x01@\x01H\x03P\x02X\x03`\x01\x12|\n" + @@ -7871,7 +8143,7 @@ func file_saas_accounts_v1_datasource_proto_rawDescGZIP() []byte { } var file_saas_accounts_v1_datasource_proto_enumTypes = make([]protoimpl.EnumInfo, 14) -var file_saas_accounts_v1_datasource_proto_msgTypes = make([]protoimpl.MessageInfo, 89) +var file_saas_accounts_v1_datasource_proto_msgTypes = make([]protoimpl.MessageInfo, 92) var file_saas_accounts_v1_datasource_proto_goTypes = []any{ (DatasourceProvider)(0), // 0: saas.accounts.v1.DatasourceProvider (DatasourceStatus)(0), // 1: saas.accounts.v1.DatasourceStatus @@ -7897,90 +8169,93 @@ var file_saas_accounts_v1_datasource_proto_goTypes = []any{ (*AddGitHubSourceResponse)(nil), // 21: saas.accounts.v1.AddGitHubSourceResponse (*AddSourceRequest)(nil), // 22: saas.accounts.v1.AddSourceRequest (*AddSourceResponse)(nil), // 23: saas.accounts.v1.AddSourceResponse - (*DatasourceConfigField)(nil), // 24: saas.accounts.v1.DatasourceConfigField - (*DatasourceConnectorBudget)(nil), // 25: saas.accounts.v1.DatasourceConnectorBudget - (*DatasourceProviderDescriptor)(nil), // 26: saas.accounts.v1.DatasourceProviderDescriptor - (*GetDatasourceCatalogRequest)(nil), // 27: saas.accounts.v1.GetDatasourceCatalogRequest - (*GetDatasourceCatalogResponse)(nil), // 28: saas.accounts.v1.GetDatasourceCatalogResponse - (*ListSourcesRequest)(nil), // 29: saas.accounts.v1.ListSourcesRequest - (*ListSourcesResponse)(nil), // 30: saas.accounts.v1.ListSourcesResponse - (*GetSourceRequest)(nil), // 31: saas.accounts.v1.GetSourceRequest - (*GetSourceResponse)(nil), // 32: saas.accounts.v1.GetSourceResponse - (*SyncSourceRequest)(nil), // 33: saas.accounts.v1.SyncSourceRequest - (*SyncSourceResponse)(nil), // 34: saas.accounts.v1.SyncSourceResponse - (*GetSourceSyncRequest)(nil), // 35: saas.accounts.v1.GetSourceSyncRequest - (*SourceSyncDelivery)(nil), // 36: saas.accounts.v1.SourceSyncDelivery - (*SourceSyncChanges)(nil), // 37: saas.accounts.v1.SourceSyncChanges - (*SourceSyncFailure)(nil), // 38: saas.accounts.v1.SourceSyncFailure - (*SourceSyncProgress)(nil), // 39: saas.accounts.v1.SourceSyncProgress - (*GetSourceSyncResponse)(nil), // 40: saas.accounts.v1.GetSourceSyncResponse - (*DeleteSourceRequest)(nil), // 41: saas.accounts.v1.DeleteSourceRequest - (*DeleteSourceResponse)(nil), // 42: saas.accounts.v1.DeleteSourceResponse - (*GitHubAppRepository)(nil), // 43: saas.accounts.v1.GitHubAppRepository - (*BeginGitHubAppSetupRequest)(nil), // 44: saas.accounts.v1.BeginGitHubAppSetupRequest - (*BeginGitHubAppSetupResponse)(nil), // 45: saas.accounts.v1.BeginGitHubAppSetupResponse - (*CompleteGitHubAppSetupRequest)(nil), // 46: saas.accounts.v1.CompleteGitHubAppSetupRequest - (*CompleteGitHubAppSetupResponse)(nil), // 47: saas.accounts.v1.CompleteGitHubAppSetupResponse - (*MigrateGitHubSourceToAppRequest)(nil), // 48: saas.accounts.v1.MigrateGitHubSourceToAppRequest - (*MigrateGitHubSourceToAppResponse)(nil), // 49: saas.accounts.v1.MigrateGitHubSourceToAppResponse - (*DatasourceAccountLink)(nil), // 50: saas.accounts.v1.DatasourceAccountLink - (*DatasourceGroupBinding)(nil), // 51: saas.accounts.v1.DatasourceGroupBinding - (*DatasourceVerifiedDomain)(nil), // 52: saas.accounts.v1.DatasourceVerifiedDomain - (*DatasourceDirectoryTeam)(nil), // 53: saas.accounts.v1.DatasourceDirectoryTeam - (*BeginDatasourceAccountLinkRequest)(nil), // 54: saas.accounts.v1.BeginDatasourceAccountLinkRequest - (*BeginDatasourceAccountLinkResponse)(nil), // 55: saas.accounts.v1.BeginDatasourceAccountLinkResponse - (*CompleteDatasourceAccountLinkRequest)(nil), // 56: saas.accounts.v1.CompleteDatasourceAccountLinkRequest - (*CompleteDatasourceAccountLinkResponse)(nil), // 57: saas.accounts.v1.CompleteDatasourceAccountLinkResponse - (*ListMyDatasourceAccountLinksRequest)(nil), // 58: saas.accounts.v1.ListMyDatasourceAccountLinksRequest - (*ListMyDatasourceAccountLinksResponse)(nil), // 59: saas.accounts.v1.ListMyDatasourceAccountLinksResponse - (*DeleteDatasourceAccountLinkRequest)(nil), // 60: saas.accounts.v1.DeleteDatasourceAccountLinkRequest - (*DeleteDatasourceAccountLinkResponse)(nil), // 61: saas.accounts.v1.DeleteDatasourceAccountLinkResponse - (*GetDatasourceDirectoryRequest)(nil), // 62: saas.accounts.v1.GetDatasourceDirectoryRequest - (*GetDatasourceDirectoryResponse)(nil), // 63: saas.accounts.v1.GetDatasourceDirectoryResponse - (*BindDatasourceGroupRequest)(nil), // 64: saas.accounts.v1.BindDatasourceGroupRequest - (*BindDatasourceGroupResponse)(nil), // 65: saas.accounts.v1.BindDatasourceGroupResponse - (*UnbindDatasourceGroupRequest)(nil), // 66: saas.accounts.v1.UnbindDatasourceGroupRequest - (*UnbindDatasourceGroupResponse)(nil), // 67: saas.accounts.v1.UnbindDatasourceGroupResponse - (*ClaimDatasourceDomainRequest)(nil), // 68: saas.accounts.v1.ClaimDatasourceDomainRequest - (*ClaimDatasourceDomainResponse)(nil), // 69: saas.accounts.v1.ClaimDatasourceDomainResponse - (*VerifyDatasourceDomainRequest)(nil), // 70: saas.accounts.v1.VerifyDatasourceDomainRequest - (*VerifyDatasourceDomainResponse)(nil), // 71: saas.accounts.v1.VerifyDatasourceDomainResponse - (*DeleteDatasourceDomainRequest)(nil), // 72: saas.accounts.v1.DeleteDatasourceDomainRequest - (*DeleteDatasourceDomainResponse)(nil), // 73: saas.accounts.v1.DeleteDatasourceDomainResponse - (*SourceDelegation)(nil), // 74: saas.accounts.v1.SourceDelegation - (*ListSourceDelegationsRequest)(nil), // 75: saas.accounts.v1.ListSourceDelegationsRequest - (*ListSourceDelegationsResponse)(nil), // 76: saas.accounts.v1.ListSourceDelegationsResponse - (*RevokeSourceDelegationRequest)(nil), // 77: saas.accounts.v1.RevokeSourceDelegationRequest - (*RevokeSourceDelegationResponse)(nil), // 78: saas.accounts.v1.RevokeSourceDelegationResponse - (*TriggerIdentity)(nil), // 79: saas.accounts.v1.TriggerIdentity - (*DeclaredTriggerSchedule)(nil), // 80: saas.accounts.v1.DeclaredTriggerSchedule - (*TriggerDelegationReference)(nil), // 81: saas.accounts.v1.TriggerDelegationReference - (*TriggerChildContext)(nil), // 82: saas.accounts.v1.TriggerChildContext - (*SourceTriggerDeclaration)(nil), // 83: saas.accounts.v1.SourceTriggerDeclaration - (*DelegateSourceTriggersRequest)(nil), // 84: saas.accounts.v1.DelegateSourceTriggersRequest - (*DelegateSourceTriggersResponse)(nil), // 85: saas.accounts.v1.DelegateSourceTriggersResponse - (*GetSourceTriggerDeclarationRequest)(nil), // 86: saas.accounts.v1.GetSourceTriggerDeclarationRequest - (*GetSourceTriggerDeclarationResponse)(nil), // 87: saas.accounts.v1.GetSourceTriggerDeclarationResponse - (*ReconcileSourceRequest)(nil), // 88: saas.accounts.v1.ReconcileSourceRequest - (*ReconcileSourceResponse)(nil), // 89: saas.accounts.v1.ReconcileSourceResponse - (*LookupReconcileSourceRequest)(nil), // 90: saas.accounts.v1.LookupReconcileSourceRequest - (*SourceOperation)(nil), // 91: saas.accounts.v1.SourceOperation - (*DeclareSourceOperationsRequest)(nil), // 92: saas.accounts.v1.DeclareSourceOperationsRequest - (*DeclareSourceOperationsResponse)(nil), // 93: saas.accounts.v1.DeclareSourceOperationsResponse - (*ListSourceOperationsRequest)(nil), // 94: saas.accounts.v1.ListSourceOperationsRequest - (*ListSourceOperationsResponse)(nil), // 95: saas.accounts.v1.ListSourceOperationsResponse - (*InvokeSourceOperationRequest)(nil), // 96: saas.accounts.v1.InvokeSourceOperationRequest - (*SourceOperationReceipt)(nil), // 97: saas.accounts.v1.SourceOperationReceipt - (*InvokeSourceOperationResponse)(nil), // 98: saas.accounts.v1.InvokeSourceOperationResponse - (*LookupInvokeSourceOperationRequest)(nil), // 99: saas.accounts.v1.LookupInvokeSourceOperationRequest - (*PruneSourceOperationReceiptsRequest)(nil), // 100: saas.accounts.v1.PruneSourceOperationReceiptsRequest - (*PruneSourceOperationReceiptsResponse)(nil), // 101: saas.accounts.v1.PruneSourceOperationReceiptsResponse - (*LookupPruneSourceOperationReceiptsRequest)(nil), // 102: saas.accounts.v1.LookupPruneSourceOperationReceiptsRequest - (*timestamppb.Timestamp)(nil), // 103: google.protobuf.Timestamp - (*durationpb.Duration)(nil), // 104: google.protobuf.Duration - (v1.JobState)(0), // 105: saas.jobs.v1.JobState - (*v1.JobExecutionReference)(nil), // 106: saas.jobs.v1.JobExecutionReference - (*structpb.Struct)(nil), // 107: google.protobuf.Struct + (*UpdateSourceRequest)(nil), // 24: saas.accounts.v1.UpdateSourceRequest + (*SourceFetchScope)(nil), // 25: saas.accounts.v1.SourceFetchScope + (*UpdateSourceResponse)(nil), // 26: saas.accounts.v1.UpdateSourceResponse + (*DatasourceConfigField)(nil), // 27: saas.accounts.v1.DatasourceConfigField + (*DatasourceConnectorBudget)(nil), // 28: saas.accounts.v1.DatasourceConnectorBudget + (*DatasourceProviderDescriptor)(nil), // 29: saas.accounts.v1.DatasourceProviderDescriptor + (*GetDatasourceCatalogRequest)(nil), // 30: saas.accounts.v1.GetDatasourceCatalogRequest + (*GetDatasourceCatalogResponse)(nil), // 31: saas.accounts.v1.GetDatasourceCatalogResponse + (*ListSourcesRequest)(nil), // 32: saas.accounts.v1.ListSourcesRequest + (*ListSourcesResponse)(nil), // 33: saas.accounts.v1.ListSourcesResponse + (*GetSourceRequest)(nil), // 34: saas.accounts.v1.GetSourceRequest + (*GetSourceResponse)(nil), // 35: saas.accounts.v1.GetSourceResponse + (*SyncSourceRequest)(nil), // 36: saas.accounts.v1.SyncSourceRequest + (*SyncSourceResponse)(nil), // 37: saas.accounts.v1.SyncSourceResponse + (*GetSourceSyncRequest)(nil), // 38: saas.accounts.v1.GetSourceSyncRequest + (*SourceSyncDelivery)(nil), // 39: saas.accounts.v1.SourceSyncDelivery + (*SourceSyncChanges)(nil), // 40: saas.accounts.v1.SourceSyncChanges + (*SourceSyncFailure)(nil), // 41: saas.accounts.v1.SourceSyncFailure + (*SourceSyncProgress)(nil), // 42: saas.accounts.v1.SourceSyncProgress + (*GetSourceSyncResponse)(nil), // 43: saas.accounts.v1.GetSourceSyncResponse + (*DeleteSourceRequest)(nil), // 44: saas.accounts.v1.DeleteSourceRequest + (*DeleteSourceResponse)(nil), // 45: saas.accounts.v1.DeleteSourceResponse + (*GitHubAppRepository)(nil), // 46: saas.accounts.v1.GitHubAppRepository + (*BeginGitHubAppSetupRequest)(nil), // 47: saas.accounts.v1.BeginGitHubAppSetupRequest + (*BeginGitHubAppSetupResponse)(nil), // 48: saas.accounts.v1.BeginGitHubAppSetupResponse + (*CompleteGitHubAppSetupRequest)(nil), // 49: saas.accounts.v1.CompleteGitHubAppSetupRequest + (*CompleteGitHubAppSetupResponse)(nil), // 50: saas.accounts.v1.CompleteGitHubAppSetupResponse + (*MigrateGitHubSourceToAppRequest)(nil), // 51: saas.accounts.v1.MigrateGitHubSourceToAppRequest + (*MigrateGitHubSourceToAppResponse)(nil), // 52: saas.accounts.v1.MigrateGitHubSourceToAppResponse + (*DatasourceAccountLink)(nil), // 53: saas.accounts.v1.DatasourceAccountLink + (*DatasourceGroupBinding)(nil), // 54: saas.accounts.v1.DatasourceGroupBinding + (*DatasourceVerifiedDomain)(nil), // 55: saas.accounts.v1.DatasourceVerifiedDomain + (*DatasourceDirectoryTeam)(nil), // 56: saas.accounts.v1.DatasourceDirectoryTeam + (*BeginDatasourceAccountLinkRequest)(nil), // 57: saas.accounts.v1.BeginDatasourceAccountLinkRequest + (*BeginDatasourceAccountLinkResponse)(nil), // 58: saas.accounts.v1.BeginDatasourceAccountLinkResponse + (*CompleteDatasourceAccountLinkRequest)(nil), // 59: saas.accounts.v1.CompleteDatasourceAccountLinkRequest + (*CompleteDatasourceAccountLinkResponse)(nil), // 60: saas.accounts.v1.CompleteDatasourceAccountLinkResponse + (*ListMyDatasourceAccountLinksRequest)(nil), // 61: saas.accounts.v1.ListMyDatasourceAccountLinksRequest + (*ListMyDatasourceAccountLinksResponse)(nil), // 62: saas.accounts.v1.ListMyDatasourceAccountLinksResponse + (*DeleteDatasourceAccountLinkRequest)(nil), // 63: saas.accounts.v1.DeleteDatasourceAccountLinkRequest + (*DeleteDatasourceAccountLinkResponse)(nil), // 64: saas.accounts.v1.DeleteDatasourceAccountLinkResponse + (*GetDatasourceDirectoryRequest)(nil), // 65: saas.accounts.v1.GetDatasourceDirectoryRequest + (*GetDatasourceDirectoryResponse)(nil), // 66: saas.accounts.v1.GetDatasourceDirectoryResponse + (*BindDatasourceGroupRequest)(nil), // 67: saas.accounts.v1.BindDatasourceGroupRequest + (*BindDatasourceGroupResponse)(nil), // 68: saas.accounts.v1.BindDatasourceGroupResponse + (*UnbindDatasourceGroupRequest)(nil), // 69: saas.accounts.v1.UnbindDatasourceGroupRequest + (*UnbindDatasourceGroupResponse)(nil), // 70: saas.accounts.v1.UnbindDatasourceGroupResponse + (*ClaimDatasourceDomainRequest)(nil), // 71: saas.accounts.v1.ClaimDatasourceDomainRequest + (*ClaimDatasourceDomainResponse)(nil), // 72: saas.accounts.v1.ClaimDatasourceDomainResponse + (*VerifyDatasourceDomainRequest)(nil), // 73: saas.accounts.v1.VerifyDatasourceDomainRequest + (*VerifyDatasourceDomainResponse)(nil), // 74: saas.accounts.v1.VerifyDatasourceDomainResponse + (*DeleteDatasourceDomainRequest)(nil), // 75: saas.accounts.v1.DeleteDatasourceDomainRequest + (*DeleteDatasourceDomainResponse)(nil), // 76: saas.accounts.v1.DeleteDatasourceDomainResponse + (*SourceDelegation)(nil), // 77: saas.accounts.v1.SourceDelegation + (*ListSourceDelegationsRequest)(nil), // 78: saas.accounts.v1.ListSourceDelegationsRequest + (*ListSourceDelegationsResponse)(nil), // 79: saas.accounts.v1.ListSourceDelegationsResponse + (*RevokeSourceDelegationRequest)(nil), // 80: saas.accounts.v1.RevokeSourceDelegationRequest + (*RevokeSourceDelegationResponse)(nil), // 81: saas.accounts.v1.RevokeSourceDelegationResponse + (*TriggerIdentity)(nil), // 82: saas.accounts.v1.TriggerIdentity + (*DeclaredTriggerSchedule)(nil), // 83: saas.accounts.v1.DeclaredTriggerSchedule + (*TriggerDelegationReference)(nil), // 84: saas.accounts.v1.TriggerDelegationReference + (*TriggerChildContext)(nil), // 85: saas.accounts.v1.TriggerChildContext + (*SourceTriggerDeclaration)(nil), // 86: saas.accounts.v1.SourceTriggerDeclaration + (*DelegateSourceTriggersRequest)(nil), // 87: saas.accounts.v1.DelegateSourceTriggersRequest + (*DelegateSourceTriggersResponse)(nil), // 88: saas.accounts.v1.DelegateSourceTriggersResponse + (*GetSourceTriggerDeclarationRequest)(nil), // 89: saas.accounts.v1.GetSourceTriggerDeclarationRequest + (*GetSourceTriggerDeclarationResponse)(nil), // 90: saas.accounts.v1.GetSourceTriggerDeclarationResponse + (*ReconcileSourceRequest)(nil), // 91: saas.accounts.v1.ReconcileSourceRequest + (*ReconcileSourceResponse)(nil), // 92: saas.accounts.v1.ReconcileSourceResponse + (*LookupReconcileSourceRequest)(nil), // 93: saas.accounts.v1.LookupReconcileSourceRequest + (*SourceOperation)(nil), // 94: saas.accounts.v1.SourceOperation + (*DeclareSourceOperationsRequest)(nil), // 95: saas.accounts.v1.DeclareSourceOperationsRequest + (*DeclareSourceOperationsResponse)(nil), // 96: saas.accounts.v1.DeclareSourceOperationsResponse + (*ListSourceOperationsRequest)(nil), // 97: saas.accounts.v1.ListSourceOperationsRequest + (*ListSourceOperationsResponse)(nil), // 98: saas.accounts.v1.ListSourceOperationsResponse + (*InvokeSourceOperationRequest)(nil), // 99: saas.accounts.v1.InvokeSourceOperationRequest + (*SourceOperationReceipt)(nil), // 100: saas.accounts.v1.SourceOperationReceipt + (*InvokeSourceOperationResponse)(nil), // 101: saas.accounts.v1.InvokeSourceOperationResponse + (*LookupInvokeSourceOperationRequest)(nil), // 102: saas.accounts.v1.LookupInvokeSourceOperationRequest + (*PruneSourceOperationReceiptsRequest)(nil), // 103: saas.accounts.v1.PruneSourceOperationReceiptsRequest + (*PruneSourceOperationReceiptsResponse)(nil), // 104: saas.accounts.v1.PruneSourceOperationReceiptsResponse + (*LookupPruneSourceOperationReceiptsRequest)(nil), // 105: saas.accounts.v1.LookupPruneSourceOperationReceiptsRequest + (*timestamppb.Timestamp)(nil), // 106: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 107: google.protobuf.Duration + (v1.JobState)(0), // 108: saas.jobs.v1.JobState + (*v1.JobExecutionReference)(nil), // 109: saas.jobs.v1.JobExecutionReference + (*structpb.Struct)(nil), // 110: google.protobuf.Struct } var file_saas_accounts_v1_datasource_proto_depIdxs = []int32{ 12, // 0: saas.accounts.v1.ApiOAuth2Config.grant:type_name -> saas.accounts.v1.ApiOAuth2Config.Grant @@ -7989,15 +8264,15 @@ var file_saas_accounts_v1_datasource_proto_depIdxs = []int32{ 0, // 3: saas.accounts.v1.Datasource.provider:type_name -> saas.accounts.v1.DatasourceProvider 14, // 4: saas.accounts.v1.Datasource.github:type_name -> saas.accounts.v1.GitHubDatasourceConfig 1, // 5: saas.accounts.v1.Datasource.status:type_name -> saas.accounts.v1.DatasourceStatus - 103, // 6: saas.accounts.v1.Datasource.created_at:type_name -> google.protobuf.Timestamp - 103, // 7: saas.accounts.v1.Datasource.updated_at:type_name -> google.protobuf.Timestamp - 103, // 8: saas.accounts.v1.Datasource.last_synced_at:type_name -> google.protobuf.Timestamp + 106, // 6: saas.accounts.v1.Datasource.created_at:type_name -> google.protobuf.Timestamp + 106, // 7: saas.accounts.v1.Datasource.updated_at:type_name -> google.protobuf.Timestamp + 106, // 8: saas.accounts.v1.Datasource.last_synced_at:type_name -> google.protobuf.Timestamp 16, // 9: saas.accounts.v1.Datasource.api:type_name -> saas.accounts.v1.ApiDatasourceConfig 17, // 10: saas.accounts.v1.Datasource.crawler:type_name -> saas.accounts.v1.CrawlerDatasourceConfig 18, // 11: saas.accounts.v1.Datasource.upload:type_name -> saas.accounts.v1.UploadDatasourceConfig - 103, // 12: saas.accounts.v1.Datasource.last_ingested_at:type_name -> google.protobuf.Timestamp + 106, // 12: saas.accounts.v1.Datasource.last_ingested_at:type_name -> google.protobuf.Timestamp 2, // 13: saas.accounts.v1.Datasource.live_delivery:type_name -> saas.accounts.v1.DatasourceLiveDelivery - 104, // 14: saas.accounts.v1.Datasource.reconcile_interval:type_name -> google.protobuf.Duration + 107, // 14: saas.accounts.v1.Datasource.reconcile_interval:type_name -> google.protobuf.Duration 19, // 15: saas.accounts.v1.AddGitHubSourceResponse.datasource:type_name -> saas.accounts.v1.Datasource 0, // 16: saas.accounts.v1.AddSourceRequest.provider:type_name -> saas.accounts.v1.DatasourceProvider 14, // 17: saas.accounts.v1.AddSourceRequest.github:type_name -> saas.accounts.v1.GitHubDatasourceConfig @@ -8005,145 +8280,149 @@ var file_saas_accounts_v1_datasource_proto_depIdxs = []int32{ 17, // 19: saas.accounts.v1.AddSourceRequest.crawler:type_name -> saas.accounts.v1.CrawlerDatasourceConfig 18, // 20: saas.accounts.v1.AddSourceRequest.upload:type_name -> saas.accounts.v1.UploadDatasourceConfig 19, // 21: saas.accounts.v1.AddSourceResponse.datasource:type_name -> saas.accounts.v1.Datasource - 0, // 22: saas.accounts.v1.DatasourceProviderDescriptor.provider:type_name -> saas.accounts.v1.DatasourceProvider - 24, // 23: saas.accounts.v1.DatasourceProviderDescriptor.config_fields:type_name -> saas.accounts.v1.DatasourceConfigField - 3, // 24: saas.accounts.v1.DatasourceProviderDescriptor.supported_credential_kinds:type_name -> saas.accounts.v1.ApiCredentialKind - 4, // 25: saas.accounts.v1.DatasourceProviderDescriptor.interface:type_name -> saas.accounts.v1.DatasourceConnectorInterface - 5, // 26: saas.accounts.v1.DatasourceProviderDescriptor.credential_modes:type_name -> saas.accounts.v1.DatasourceCredentialMode - 6, // 27: saas.accounts.v1.DatasourceProviderDescriptor.readers_model:type_name -> saas.accounts.v1.DatasourceReadersModel - 25, // 28: saas.accounts.v1.DatasourceProviderDescriptor.budget:type_name -> saas.accounts.v1.DatasourceConnectorBudget - 26, // 29: saas.accounts.v1.GetDatasourceCatalogResponse.providers:type_name -> saas.accounts.v1.DatasourceProviderDescriptor - 19, // 30: saas.accounts.v1.ListSourcesResponse.datasources:type_name -> saas.accounts.v1.Datasource - 19, // 31: saas.accounts.v1.GetSourceResponse.datasource:type_name -> saas.accounts.v1.Datasource - 105, // 32: saas.accounts.v1.SourceSyncDelivery.state:type_name -> saas.jobs.v1.JobState - 106, // 33: saas.accounts.v1.SourceSyncDelivery.execution:type_name -> saas.jobs.v1.JobExecutionReference - 8, // 34: saas.accounts.v1.SourceSyncFailure.reason:type_name -> saas.accounts.v1.SourceSyncFailureReason - 103, // 35: saas.accounts.v1.SourceSyncFailure.retry_at:type_name -> google.protobuf.Timestamp - 7, // 36: saas.accounts.v1.SourceSyncProgress.phase:type_name -> saas.accounts.v1.SourceSyncPhase - 9, // 37: saas.accounts.v1.SourceSyncProgress.trigger:type_name -> saas.accounts.v1.SourceSyncTrigger - 103, // 38: saas.accounts.v1.SourceSyncProgress.queued_at:type_name -> google.protobuf.Timestamp - 103, // 39: saas.accounts.v1.SourceSyncProgress.fetching_at:type_name -> google.protobuf.Timestamp - 103, // 40: saas.accounts.v1.SourceSyncProgress.compiled_at:type_name -> google.protobuf.Timestamp - 103, // 41: saas.accounts.v1.SourceSyncProgress.handed_off_at:type_name -> google.protobuf.Timestamp - 103, // 42: saas.accounts.v1.SourceSyncProgress.finished_at:type_name -> google.protobuf.Timestamp - 37, // 43: saas.accounts.v1.SourceSyncProgress.changes:type_name -> saas.accounts.v1.SourceSyncChanges - 38, // 44: saas.accounts.v1.SourceSyncProgress.failure:type_name -> saas.accounts.v1.SourceSyncFailure - 105, // 45: saas.accounts.v1.GetSourceSyncResponse.state:type_name -> saas.jobs.v1.JobState - 36, // 46: saas.accounts.v1.GetSourceSyncResponse.deliveries:type_name -> saas.accounts.v1.SourceSyncDelivery - 39, // 47: saas.accounts.v1.GetSourceSyncResponse.progress:type_name -> saas.accounts.v1.SourceSyncProgress - 103, // 48: saas.accounts.v1.BeginGitHubAppSetupResponse.expires_at:type_name -> google.protobuf.Timestamp - 43, // 49: saas.accounts.v1.CompleteGitHubAppSetupResponse.repositories:type_name -> saas.accounts.v1.GitHubAppRepository - 19, // 50: saas.accounts.v1.MigrateGitHubSourceToAppResponse.datasource:type_name -> saas.accounts.v1.Datasource - 103, // 51: saas.accounts.v1.DatasourceAccountLink.created_at:type_name -> google.protobuf.Timestamp - 103, // 52: saas.accounts.v1.DatasourceGroupBinding.created_at:type_name -> google.protobuf.Timestamp - 10, // 53: saas.accounts.v1.DatasourceVerifiedDomain.status:type_name -> saas.accounts.v1.DatasourceDomainStatus - 103, // 54: saas.accounts.v1.DatasourceVerifiedDomain.verified_at:type_name -> google.protobuf.Timestamp - 103, // 55: saas.accounts.v1.DatasourceVerifiedDomain.created_at:type_name -> google.protobuf.Timestamp - 103, // 56: saas.accounts.v1.BeginDatasourceAccountLinkResponse.expires_at:type_name -> google.protobuf.Timestamp - 50, // 57: saas.accounts.v1.CompleteDatasourceAccountLinkResponse.link:type_name -> saas.accounts.v1.DatasourceAccountLink - 50, // 58: saas.accounts.v1.ListMyDatasourceAccountLinksResponse.links:type_name -> saas.accounts.v1.DatasourceAccountLink - 50, // 59: saas.accounts.v1.GetDatasourceDirectoryResponse.links:type_name -> saas.accounts.v1.DatasourceAccountLink - 51, // 60: saas.accounts.v1.GetDatasourceDirectoryResponse.bindings:type_name -> saas.accounts.v1.DatasourceGroupBinding - 52, // 61: saas.accounts.v1.GetDatasourceDirectoryResponse.domains:type_name -> saas.accounts.v1.DatasourceVerifiedDomain - 53, // 62: saas.accounts.v1.GetDatasourceDirectoryResponse.teams:type_name -> saas.accounts.v1.DatasourceDirectoryTeam - 51, // 63: saas.accounts.v1.BindDatasourceGroupResponse.binding:type_name -> saas.accounts.v1.DatasourceGroupBinding - 52, // 64: saas.accounts.v1.ClaimDatasourceDomainResponse.domain:type_name -> saas.accounts.v1.DatasourceVerifiedDomain - 52, // 65: saas.accounts.v1.VerifyDatasourceDomainResponse.domain:type_name -> saas.accounts.v1.DatasourceVerifiedDomain - 103, // 66: saas.accounts.v1.SourceDelegation.created_at:type_name -> google.protobuf.Timestamp - 103, // 67: saas.accounts.v1.SourceDelegation.revoked_at:type_name -> google.protobuf.Timestamp - 11, // 68: saas.accounts.v1.SourceDelegation.revocation:type_name -> saas.accounts.v1.SourceDelegationRevocation - 74, // 69: saas.accounts.v1.ListSourceDelegationsResponse.delegations:type_name -> saas.accounts.v1.SourceDelegation - 74, // 70: saas.accounts.v1.RevokeSourceDelegationResponse.delegation:type_name -> saas.accounts.v1.SourceDelegation - 103, // 71: saas.accounts.v1.TriggerDelegationReference.created_at:type_name -> google.protobuf.Timestamp - 103, // 72: saas.accounts.v1.TriggerDelegationReference.revoked_at:type_name -> google.protobuf.Timestamp - 11, // 73: saas.accounts.v1.TriggerDelegationReference.revocation:type_name -> saas.accounts.v1.SourceDelegationRevocation - 103, // 74: saas.accounts.v1.TriggerChildContext.minted_at:type_name -> google.protobuf.Timestamp - 80, // 75: saas.accounts.v1.SourceTriggerDeclaration.schedule:type_name -> saas.accounts.v1.DeclaredTriggerSchedule - 79, // 76: saas.accounts.v1.SourceTriggerDeclaration.identity:type_name -> saas.accounts.v1.TriggerIdentity - 103, // 77: saas.accounts.v1.SourceTriggerDeclaration.last_occurrence_at:type_name -> google.protobuf.Timestamp - 103, // 78: saas.accounts.v1.SourceTriggerDeclaration.next_occurrence_at:type_name -> google.protobuf.Timestamp - 81, // 79: saas.accounts.v1.DelegateSourceTriggersResponse.delegations:type_name -> saas.accounts.v1.TriggerDelegationReference - 83, // 80: saas.accounts.v1.GetSourceTriggerDeclarationResponse.declaration:type_name -> saas.accounts.v1.SourceTriggerDeclaration - 81, // 81: saas.accounts.v1.GetSourceTriggerDeclarationResponse.delegations:type_name -> saas.accounts.v1.TriggerDelegationReference - 82, // 82: saas.accounts.v1.GetSourceTriggerDeclarationResponse.children:type_name -> saas.accounts.v1.TriggerChildContext - 107, // 83: saas.accounts.v1.SourceOperation.input_schema:type_name -> google.protobuf.Struct - 107, // 84: saas.accounts.v1.SourceOperation.output_schema:type_name -> google.protobuf.Struct - 13, // 85: saas.accounts.v1.SourceOperation.effect:type_name -> saas.accounts.v1.SourceOperation.Effect - 91, // 86: saas.accounts.v1.DeclareSourceOperationsRequest.operations:type_name -> saas.accounts.v1.SourceOperation - 91, // 87: saas.accounts.v1.DeclareSourceOperationsResponse.operations:type_name -> saas.accounts.v1.SourceOperation - 91, // 88: saas.accounts.v1.ListSourceOperationsResponse.operations:type_name -> saas.accounts.v1.SourceOperation - 97, // 89: saas.accounts.v1.InvokeSourceOperationResponse.receipt:type_name -> saas.accounts.v1.SourceOperationReceipt - 100, // 90: saas.accounts.v1.DatasourceService.PruneSourceOperationReceipts:input_type -> saas.accounts.v1.PruneSourceOperationReceiptsRequest - 102, // 91: saas.accounts.v1.DatasourceService.LookupPruneSourceOperationReceipts:input_type -> saas.accounts.v1.LookupPruneSourceOperationReceiptsRequest - 96, // 92: saas.accounts.v1.DatasourceService.InvokeSourceOperation:input_type -> saas.accounts.v1.InvokeSourceOperationRequest - 99, // 93: saas.accounts.v1.DatasourceService.LookupInvokeSourceOperation:input_type -> saas.accounts.v1.LookupInvokeSourceOperationRequest - 94, // 94: saas.accounts.v1.DatasourceService.ListSourceOperations:input_type -> saas.accounts.v1.ListSourceOperationsRequest - 92, // 95: saas.accounts.v1.DatasourceService.DeclareSourceOperations:input_type -> saas.accounts.v1.DeclareSourceOperationsRequest - 20, // 96: saas.accounts.v1.DatasourceService.AddGitHubSource:input_type -> saas.accounts.v1.AddGitHubSourceRequest - 22, // 97: saas.accounts.v1.DatasourceService.AddSource:input_type -> saas.accounts.v1.AddSourceRequest - 27, // 98: saas.accounts.v1.DatasourceService.GetDatasourceCatalog:input_type -> saas.accounts.v1.GetDatasourceCatalogRequest - 29, // 99: saas.accounts.v1.DatasourceService.ListSources:input_type -> saas.accounts.v1.ListSourcesRequest - 31, // 100: saas.accounts.v1.DatasourceService.GetSource:input_type -> saas.accounts.v1.GetSourceRequest - 33, // 101: saas.accounts.v1.DatasourceService.SyncSource:input_type -> saas.accounts.v1.SyncSourceRequest - 35, // 102: saas.accounts.v1.DatasourceService.GetSourceSync:input_type -> saas.accounts.v1.GetSourceSyncRequest - 41, // 103: saas.accounts.v1.DatasourceService.DeleteSource:input_type -> saas.accounts.v1.DeleteSourceRequest - 44, // 104: saas.accounts.v1.DatasourceService.BeginGitHubAppSetup:input_type -> saas.accounts.v1.BeginGitHubAppSetupRequest - 46, // 105: saas.accounts.v1.DatasourceService.CompleteGitHubAppSetup:input_type -> saas.accounts.v1.CompleteGitHubAppSetupRequest - 48, // 106: saas.accounts.v1.DatasourceService.MigrateGitHubSourceToApp:input_type -> saas.accounts.v1.MigrateGitHubSourceToAppRequest - 54, // 107: saas.accounts.v1.DatasourceService.BeginDatasourceAccountLink:input_type -> saas.accounts.v1.BeginDatasourceAccountLinkRequest - 56, // 108: saas.accounts.v1.DatasourceService.CompleteDatasourceAccountLink:input_type -> saas.accounts.v1.CompleteDatasourceAccountLinkRequest - 58, // 109: saas.accounts.v1.DatasourceService.ListMyDatasourceAccountLinks:input_type -> saas.accounts.v1.ListMyDatasourceAccountLinksRequest - 60, // 110: saas.accounts.v1.DatasourceService.DeleteDatasourceAccountLink:input_type -> saas.accounts.v1.DeleteDatasourceAccountLinkRequest - 62, // 111: saas.accounts.v1.DatasourceService.GetDatasourceDirectory:input_type -> saas.accounts.v1.GetDatasourceDirectoryRequest - 64, // 112: saas.accounts.v1.DatasourceService.BindDatasourceGroup:input_type -> saas.accounts.v1.BindDatasourceGroupRequest - 66, // 113: saas.accounts.v1.DatasourceService.UnbindDatasourceGroup:input_type -> saas.accounts.v1.UnbindDatasourceGroupRequest - 68, // 114: saas.accounts.v1.DatasourceService.ClaimDatasourceDomain:input_type -> saas.accounts.v1.ClaimDatasourceDomainRequest - 70, // 115: saas.accounts.v1.DatasourceService.VerifyDatasourceDomain:input_type -> saas.accounts.v1.VerifyDatasourceDomainRequest - 72, // 116: saas.accounts.v1.DatasourceService.DeleteDatasourceDomain:input_type -> saas.accounts.v1.DeleteDatasourceDomainRequest - 75, // 117: saas.accounts.v1.DatasourceService.ListSourceDelegations:input_type -> saas.accounts.v1.ListSourceDelegationsRequest - 77, // 118: saas.accounts.v1.DatasourceService.RevokeSourceDelegation:input_type -> saas.accounts.v1.RevokeSourceDelegationRequest - 84, // 119: saas.accounts.v1.DatasourceService.DelegateSourceTriggers:input_type -> saas.accounts.v1.DelegateSourceTriggersRequest - 86, // 120: saas.accounts.v1.DatasourceService.GetSourceTriggerDeclaration:input_type -> saas.accounts.v1.GetSourceTriggerDeclarationRequest - 88, // 121: saas.accounts.v1.DatasourceService.ReconcileSource:input_type -> saas.accounts.v1.ReconcileSourceRequest - 90, // 122: saas.accounts.v1.DatasourceService.LookupReconcileSource:input_type -> saas.accounts.v1.LookupReconcileSourceRequest - 101, // 123: saas.accounts.v1.DatasourceService.PruneSourceOperationReceipts:output_type -> saas.accounts.v1.PruneSourceOperationReceiptsResponse - 101, // 124: saas.accounts.v1.DatasourceService.LookupPruneSourceOperationReceipts:output_type -> saas.accounts.v1.PruneSourceOperationReceiptsResponse - 98, // 125: saas.accounts.v1.DatasourceService.InvokeSourceOperation:output_type -> saas.accounts.v1.InvokeSourceOperationResponse - 98, // 126: saas.accounts.v1.DatasourceService.LookupInvokeSourceOperation:output_type -> saas.accounts.v1.InvokeSourceOperationResponse - 95, // 127: saas.accounts.v1.DatasourceService.ListSourceOperations:output_type -> saas.accounts.v1.ListSourceOperationsResponse - 93, // 128: saas.accounts.v1.DatasourceService.DeclareSourceOperations:output_type -> saas.accounts.v1.DeclareSourceOperationsResponse - 21, // 129: saas.accounts.v1.DatasourceService.AddGitHubSource:output_type -> saas.accounts.v1.AddGitHubSourceResponse - 23, // 130: saas.accounts.v1.DatasourceService.AddSource:output_type -> saas.accounts.v1.AddSourceResponse - 28, // 131: saas.accounts.v1.DatasourceService.GetDatasourceCatalog:output_type -> saas.accounts.v1.GetDatasourceCatalogResponse - 30, // 132: saas.accounts.v1.DatasourceService.ListSources:output_type -> saas.accounts.v1.ListSourcesResponse - 32, // 133: saas.accounts.v1.DatasourceService.GetSource:output_type -> saas.accounts.v1.GetSourceResponse - 34, // 134: saas.accounts.v1.DatasourceService.SyncSource:output_type -> saas.accounts.v1.SyncSourceResponse - 40, // 135: saas.accounts.v1.DatasourceService.GetSourceSync:output_type -> saas.accounts.v1.GetSourceSyncResponse - 42, // 136: saas.accounts.v1.DatasourceService.DeleteSource:output_type -> saas.accounts.v1.DeleteSourceResponse - 45, // 137: saas.accounts.v1.DatasourceService.BeginGitHubAppSetup:output_type -> saas.accounts.v1.BeginGitHubAppSetupResponse - 47, // 138: saas.accounts.v1.DatasourceService.CompleteGitHubAppSetup:output_type -> saas.accounts.v1.CompleteGitHubAppSetupResponse - 49, // 139: saas.accounts.v1.DatasourceService.MigrateGitHubSourceToApp:output_type -> saas.accounts.v1.MigrateGitHubSourceToAppResponse - 55, // 140: saas.accounts.v1.DatasourceService.BeginDatasourceAccountLink:output_type -> saas.accounts.v1.BeginDatasourceAccountLinkResponse - 57, // 141: saas.accounts.v1.DatasourceService.CompleteDatasourceAccountLink:output_type -> saas.accounts.v1.CompleteDatasourceAccountLinkResponse - 59, // 142: saas.accounts.v1.DatasourceService.ListMyDatasourceAccountLinks:output_type -> saas.accounts.v1.ListMyDatasourceAccountLinksResponse - 61, // 143: saas.accounts.v1.DatasourceService.DeleteDatasourceAccountLink:output_type -> saas.accounts.v1.DeleteDatasourceAccountLinkResponse - 63, // 144: saas.accounts.v1.DatasourceService.GetDatasourceDirectory:output_type -> saas.accounts.v1.GetDatasourceDirectoryResponse - 65, // 145: saas.accounts.v1.DatasourceService.BindDatasourceGroup:output_type -> saas.accounts.v1.BindDatasourceGroupResponse - 67, // 146: saas.accounts.v1.DatasourceService.UnbindDatasourceGroup:output_type -> saas.accounts.v1.UnbindDatasourceGroupResponse - 69, // 147: saas.accounts.v1.DatasourceService.ClaimDatasourceDomain:output_type -> saas.accounts.v1.ClaimDatasourceDomainResponse - 71, // 148: saas.accounts.v1.DatasourceService.VerifyDatasourceDomain:output_type -> saas.accounts.v1.VerifyDatasourceDomainResponse - 73, // 149: saas.accounts.v1.DatasourceService.DeleteDatasourceDomain:output_type -> saas.accounts.v1.DeleteDatasourceDomainResponse - 76, // 150: saas.accounts.v1.DatasourceService.ListSourceDelegations:output_type -> saas.accounts.v1.ListSourceDelegationsResponse - 78, // 151: saas.accounts.v1.DatasourceService.RevokeSourceDelegation:output_type -> saas.accounts.v1.RevokeSourceDelegationResponse - 85, // 152: saas.accounts.v1.DatasourceService.DelegateSourceTriggers:output_type -> saas.accounts.v1.DelegateSourceTriggersResponse - 87, // 153: saas.accounts.v1.DatasourceService.GetSourceTriggerDeclaration:output_type -> saas.accounts.v1.GetSourceTriggerDeclarationResponse - 89, // 154: saas.accounts.v1.DatasourceService.ReconcileSource:output_type -> saas.accounts.v1.ReconcileSourceResponse - 89, // 155: saas.accounts.v1.DatasourceService.LookupReconcileSource:output_type -> saas.accounts.v1.ReconcileSourceResponse - 123, // [123:156] is the sub-list for method output_type - 90, // [90:123] is the sub-list for method input_type - 90, // [90:90] is the sub-list for extension type_name - 90, // [90:90] is the sub-list for extension extendee - 0, // [0:90] is the sub-list for field type_name + 25, // 22: saas.accounts.v1.UpdateSourceRequest.expected:type_name -> saas.accounts.v1.SourceFetchScope + 19, // 23: saas.accounts.v1.UpdateSourceResponse.datasource:type_name -> saas.accounts.v1.Datasource + 0, // 24: saas.accounts.v1.DatasourceProviderDescriptor.provider:type_name -> saas.accounts.v1.DatasourceProvider + 27, // 25: saas.accounts.v1.DatasourceProviderDescriptor.config_fields:type_name -> saas.accounts.v1.DatasourceConfigField + 3, // 26: saas.accounts.v1.DatasourceProviderDescriptor.supported_credential_kinds:type_name -> saas.accounts.v1.ApiCredentialKind + 4, // 27: saas.accounts.v1.DatasourceProviderDescriptor.interface:type_name -> saas.accounts.v1.DatasourceConnectorInterface + 5, // 28: saas.accounts.v1.DatasourceProviderDescriptor.credential_modes:type_name -> saas.accounts.v1.DatasourceCredentialMode + 6, // 29: saas.accounts.v1.DatasourceProviderDescriptor.readers_model:type_name -> saas.accounts.v1.DatasourceReadersModel + 28, // 30: saas.accounts.v1.DatasourceProviderDescriptor.budget:type_name -> saas.accounts.v1.DatasourceConnectorBudget + 29, // 31: saas.accounts.v1.GetDatasourceCatalogResponse.providers:type_name -> saas.accounts.v1.DatasourceProviderDescriptor + 19, // 32: saas.accounts.v1.ListSourcesResponse.datasources:type_name -> saas.accounts.v1.Datasource + 19, // 33: saas.accounts.v1.GetSourceResponse.datasource:type_name -> saas.accounts.v1.Datasource + 108, // 34: saas.accounts.v1.SourceSyncDelivery.state:type_name -> saas.jobs.v1.JobState + 109, // 35: saas.accounts.v1.SourceSyncDelivery.execution:type_name -> saas.jobs.v1.JobExecutionReference + 8, // 36: saas.accounts.v1.SourceSyncFailure.reason:type_name -> saas.accounts.v1.SourceSyncFailureReason + 106, // 37: saas.accounts.v1.SourceSyncFailure.retry_at:type_name -> google.protobuf.Timestamp + 7, // 38: saas.accounts.v1.SourceSyncProgress.phase:type_name -> saas.accounts.v1.SourceSyncPhase + 9, // 39: saas.accounts.v1.SourceSyncProgress.trigger:type_name -> saas.accounts.v1.SourceSyncTrigger + 106, // 40: saas.accounts.v1.SourceSyncProgress.queued_at:type_name -> google.protobuf.Timestamp + 106, // 41: saas.accounts.v1.SourceSyncProgress.fetching_at:type_name -> google.protobuf.Timestamp + 106, // 42: saas.accounts.v1.SourceSyncProgress.compiled_at:type_name -> google.protobuf.Timestamp + 106, // 43: saas.accounts.v1.SourceSyncProgress.handed_off_at:type_name -> google.protobuf.Timestamp + 106, // 44: saas.accounts.v1.SourceSyncProgress.finished_at:type_name -> google.protobuf.Timestamp + 40, // 45: saas.accounts.v1.SourceSyncProgress.changes:type_name -> saas.accounts.v1.SourceSyncChanges + 41, // 46: saas.accounts.v1.SourceSyncProgress.failure:type_name -> saas.accounts.v1.SourceSyncFailure + 108, // 47: saas.accounts.v1.GetSourceSyncResponse.state:type_name -> saas.jobs.v1.JobState + 39, // 48: saas.accounts.v1.GetSourceSyncResponse.deliveries:type_name -> saas.accounts.v1.SourceSyncDelivery + 42, // 49: saas.accounts.v1.GetSourceSyncResponse.progress:type_name -> saas.accounts.v1.SourceSyncProgress + 106, // 50: saas.accounts.v1.BeginGitHubAppSetupResponse.expires_at:type_name -> google.protobuf.Timestamp + 46, // 51: saas.accounts.v1.CompleteGitHubAppSetupResponse.repositories:type_name -> saas.accounts.v1.GitHubAppRepository + 19, // 52: saas.accounts.v1.MigrateGitHubSourceToAppResponse.datasource:type_name -> saas.accounts.v1.Datasource + 106, // 53: saas.accounts.v1.DatasourceAccountLink.created_at:type_name -> google.protobuf.Timestamp + 106, // 54: saas.accounts.v1.DatasourceGroupBinding.created_at:type_name -> google.protobuf.Timestamp + 10, // 55: saas.accounts.v1.DatasourceVerifiedDomain.status:type_name -> saas.accounts.v1.DatasourceDomainStatus + 106, // 56: saas.accounts.v1.DatasourceVerifiedDomain.verified_at:type_name -> google.protobuf.Timestamp + 106, // 57: saas.accounts.v1.DatasourceVerifiedDomain.created_at:type_name -> google.protobuf.Timestamp + 106, // 58: saas.accounts.v1.BeginDatasourceAccountLinkResponse.expires_at:type_name -> google.protobuf.Timestamp + 53, // 59: saas.accounts.v1.CompleteDatasourceAccountLinkResponse.link:type_name -> saas.accounts.v1.DatasourceAccountLink + 53, // 60: saas.accounts.v1.ListMyDatasourceAccountLinksResponse.links:type_name -> saas.accounts.v1.DatasourceAccountLink + 53, // 61: saas.accounts.v1.GetDatasourceDirectoryResponse.links:type_name -> saas.accounts.v1.DatasourceAccountLink + 54, // 62: saas.accounts.v1.GetDatasourceDirectoryResponse.bindings:type_name -> saas.accounts.v1.DatasourceGroupBinding + 55, // 63: saas.accounts.v1.GetDatasourceDirectoryResponse.domains:type_name -> saas.accounts.v1.DatasourceVerifiedDomain + 56, // 64: saas.accounts.v1.GetDatasourceDirectoryResponse.teams:type_name -> saas.accounts.v1.DatasourceDirectoryTeam + 54, // 65: saas.accounts.v1.BindDatasourceGroupResponse.binding:type_name -> saas.accounts.v1.DatasourceGroupBinding + 55, // 66: saas.accounts.v1.ClaimDatasourceDomainResponse.domain:type_name -> saas.accounts.v1.DatasourceVerifiedDomain + 55, // 67: saas.accounts.v1.VerifyDatasourceDomainResponse.domain:type_name -> saas.accounts.v1.DatasourceVerifiedDomain + 106, // 68: saas.accounts.v1.SourceDelegation.created_at:type_name -> google.protobuf.Timestamp + 106, // 69: saas.accounts.v1.SourceDelegation.revoked_at:type_name -> google.protobuf.Timestamp + 11, // 70: saas.accounts.v1.SourceDelegation.revocation:type_name -> saas.accounts.v1.SourceDelegationRevocation + 77, // 71: saas.accounts.v1.ListSourceDelegationsResponse.delegations:type_name -> saas.accounts.v1.SourceDelegation + 77, // 72: saas.accounts.v1.RevokeSourceDelegationResponse.delegation:type_name -> saas.accounts.v1.SourceDelegation + 106, // 73: saas.accounts.v1.TriggerDelegationReference.created_at:type_name -> google.protobuf.Timestamp + 106, // 74: saas.accounts.v1.TriggerDelegationReference.revoked_at:type_name -> google.protobuf.Timestamp + 11, // 75: saas.accounts.v1.TriggerDelegationReference.revocation:type_name -> saas.accounts.v1.SourceDelegationRevocation + 106, // 76: saas.accounts.v1.TriggerChildContext.minted_at:type_name -> google.protobuf.Timestamp + 83, // 77: saas.accounts.v1.SourceTriggerDeclaration.schedule:type_name -> saas.accounts.v1.DeclaredTriggerSchedule + 82, // 78: saas.accounts.v1.SourceTriggerDeclaration.identity:type_name -> saas.accounts.v1.TriggerIdentity + 106, // 79: saas.accounts.v1.SourceTriggerDeclaration.last_occurrence_at:type_name -> google.protobuf.Timestamp + 106, // 80: saas.accounts.v1.SourceTriggerDeclaration.next_occurrence_at:type_name -> google.protobuf.Timestamp + 84, // 81: saas.accounts.v1.DelegateSourceTriggersResponse.delegations:type_name -> saas.accounts.v1.TriggerDelegationReference + 86, // 82: saas.accounts.v1.GetSourceTriggerDeclarationResponse.declaration:type_name -> saas.accounts.v1.SourceTriggerDeclaration + 84, // 83: saas.accounts.v1.GetSourceTriggerDeclarationResponse.delegations:type_name -> saas.accounts.v1.TriggerDelegationReference + 85, // 84: saas.accounts.v1.GetSourceTriggerDeclarationResponse.children:type_name -> saas.accounts.v1.TriggerChildContext + 110, // 85: saas.accounts.v1.SourceOperation.input_schema:type_name -> google.protobuf.Struct + 110, // 86: saas.accounts.v1.SourceOperation.output_schema:type_name -> google.protobuf.Struct + 13, // 87: saas.accounts.v1.SourceOperation.effect:type_name -> saas.accounts.v1.SourceOperation.Effect + 94, // 88: saas.accounts.v1.DeclareSourceOperationsRequest.operations:type_name -> saas.accounts.v1.SourceOperation + 94, // 89: saas.accounts.v1.DeclareSourceOperationsResponse.operations:type_name -> saas.accounts.v1.SourceOperation + 94, // 90: saas.accounts.v1.ListSourceOperationsResponse.operations:type_name -> saas.accounts.v1.SourceOperation + 100, // 91: saas.accounts.v1.InvokeSourceOperationResponse.receipt:type_name -> saas.accounts.v1.SourceOperationReceipt + 103, // 92: saas.accounts.v1.DatasourceService.PruneSourceOperationReceipts:input_type -> saas.accounts.v1.PruneSourceOperationReceiptsRequest + 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 + 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 } func init() { file_saas_accounts_v1_datasource_proto_init() } @@ -8163,14 +8442,14 @@ func file_saas_accounts_v1_datasource_proto_init() { (*AddSourceRequest_BoundaryNodeId)(nil), (*AddSourceRequest_CollectionLabel)(nil), } - file_saas_accounts_v1_datasource_proto_msgTypes[61].OneofWrappers = []any{} + file_saas_accounts_v1_datasource_proto_msgTypes[64].OneofWrappers = []any{} type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_saas_accounts_v1_datasource_proto_rawDesc), len(file_saas_accounts_v1_datasource_proto_rawDesc)), NumEnums: 14, - NumMessages: 89, + NumMessages: 92, NumExtensions: 0, NumServices: 1, }, 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 cac6e4d34..acb01ed49 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 @@ -28,6 +28,7 @@ const ( DatasourceService_DeclareSourceOperations_FullMethodName = "/saas.accounts.v1.DatasourceService/DeclareSourceOperations" DatasourceService_AddGitHubSource_FullMethodName = "/saas.accounts.v1.DatasourceService/AddGitHubSource" DatasourceService_AddSource_FullMethodName = "/saas.accounts.v1.DatasourceService/AddSource" + DatasourceService_UpdateSource_FullMethodName = "/saas.accounts.v1.DatasourceService/UpdateSource" DatasourceService_GetDatasourceCatalog_FullMethodName = "/saas.accounts.v1.DatasourceService/GetDatasourceCatalog" DatasourceService_ListSources_FullMethodName = "/saas.accounts.v1.DatasourceService/ListSources" DatasourceService_GetSource_FullMethodName = "/saas.accounts.v1.DatasourceService/GetSource" @@ -93,6 +94,12 @@ type DatasourceServiceClient interface { // by provider, stores the config and the encrypted credential (and optional // webhook signing secret), and returns the non-secret projection. AddSource(ctx context.Context, in *AddSourceRequest, opts ...grpc.CallOption) (*AddSourceResponse, error) + // UpdateSource changes what an already-connected source fetches — its branch + // and its path and file-type filters — keeping its identity, its credential, + // its sync history and the collection it writes into. A change that moves + // what is fetched enqueues a sync, whose progress reports the reconfigured + // trigger so a reader can tell a re-snapshot from an ordinary one. + UpdateSource(ctx context.Context, in *UpdateSourceRequest, opts ...grpc.CallOption) (*UpdateSourceResponse, error) // GetDatasourceCatalog returns the host's connector registry: every provider, // its connect metadata, whether it conforms to the datasource connector // envelope, and whether it accepts a new source, so a client can render the @@ -304,6 +311,16 @@ func (c *datasourceServiceClient) AddSource(ctx context.Context, in *AddSourceRe return out, nil } +func (c *datasourceServiceClient) UpdateSource(ctx context.Context, in *UpdateSourceRequest, opts ...grpc.CallOption) (*UpdateSourceResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateSourceResponse) + err := c.cc.Invoke(ctx, DatasourceService_UpdateSource_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *datasourceServiceClient) GetDatasourceCatalog(ctx context.Context, in *GetDatasourceCatalogRequest, opts ...grpc.CallOption) (*GetDatasourceCatalogResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetDatasourceCatalogResponse) @@ -592,6 +609,12 @@ type DatasourceServiceServer interface { // by provider, stores the config and the encrypted credential (and optional // webhook signing secret), and returns the non-secret projection. AddSource(context.Context, *AddSourceRequest) (*AddSourceResponse, error) + // UpdateSource changes what an already-connected source fetches — its branch + // and its path and file-type filters — keeping its identity, its credential, + // its sync history and the collection it writes into. A change that moves + // what is fetched enqueues a sync, whose progress reports the reconfigured + // trigger so a reader can tell a re-snapshot from an ordinary one. + UpdateSource(context.Context, *UpdateSourceRequest) (*UpdateSourceResponse, error) // GetDatasourceCatalog returns the host's connector registry: every provider, // its connect metadata, whether it conforms to the datasource connector // envelope, and whether it accepts a new source, so a client can render the @@ -747,6 +770,9 @@ func (UnimplementedDatasourceServiceServer) AddGitHubSource(context.Context, *Ad func (UnimplementedDatasourceServiceServer) AddSource(context.Context, *AddSourceRequest) (*AddSourceResponse, error) { return nil, status.Error(codes.Unimplemented, "method AddSource not implemented") } +func (UnimplementedDatasourceServiceServer) UpdateSource(context.Context, *UpdateSourceRequest) (*UpdateSourceResponse, error) { + return nil, status.Error(codes.Unimplemented, "method UpdateSource not implemented") +} func (UnimplementedDatasourceServiceServer) GetDatasourceCatalog(context.Context, *GetDatasourceCatalogRequest) (*GetDatasourceCatalogResponse, error) { return nil, status.Error(codes.Unimplemented, "method GetDatasourceCatalog not implemented") } @@ -987,6 +1013,24 @@ func _DatasourceService_AddSource_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } +func _DatasourceService_UpdateSource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateSourceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DatasourceServiceServer).UpdateSource(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: DatasourceService_UpdateSource_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DatasourceServiceServer).UpdateSource(ctx, req.(*UpdateSourceRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _DatasourceService_GetDatasourceCatalog_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetDatasourceCatalogRequest) if err := dec(in); err != nil { @@ -1476,6 +1520,10 @@ var DatasourceService_ServiceDesc = grpc.ServiceDesc{ MethodName: "AddSource", Handler: _DatasourceService_AddSource_Handler, }, + { + MethodName: "UpdateSource", + Handler: _DatasourceService_UpdateSource_Handler, + }, { MethodName: "GetDatasourceCatalog", Handler: _DatasourceService_GetDatasourceCatalog_Handler, diff --git a/module/services/accounts/code/pkg/infra/postgres_datasource_file_filter_test.go b/module/services/accounts/code/pkg/infra/postgres_datasource_file_filter_test.go index 0f9ae891c..01cb6901d 100644 --- a/module/services/accounts/code/pkg/infra/postgres_datasource_file_filter_test.go +++ b/module/services/accounts/code/pkg/infra/postgres_datasource_file_filter_test.go @@ -91,3 +91,127 @@ func TestPostgresDatasourcePublicCredentialKindPersistsAndClearsWithAnEnvelope(t require.Equal(t, "vault:v1:envelope", updated.CredentialSecretRef) require.Equal(t, []string{".md"}, updated.FileExtensions, "the rest of the config is untouched") } + +// The fetch-scope update writes the three fields it owns and nothing else — and +// in particular rebuilds the github config envelope from the whole source, not +// from the file filter alone. Rebuilt from the filter, a public source's +// credential-less record would be dropped by every edit, and from then on every +// fetch would look for an envelope that was never stored. +func TestPostgresUpdateDatasourceSourceFetchScopeKeepsEverythingElse(t *testing.T) { + owner := seedUser(t) + org := seedOrg(t, owner) + template := installationSourceByID(t, seedDatasourceSource(t, org)) + + source := *template + source.ID = business.NewIDString() + source.CredentialSecretRef = "" + source.GitHubInstallationID = "" + source.GitHubCredentialKind = "public" + source.Branch = "main" + source.Paths = []string{"docs"} + source.FileExtensions = []string{".md"} + require.NoError(t, testStore.WithOrgTx(testCtx, org, func(ctx context.Context) error { + return testStore.InsertDatasourceSource(ctx, &source) + })) + + moved := source + moved.Branch = "release" + moved.Paths = []string{"docs", "guides"} + moved.FileExtensions = []string{".md", ".mdx"} + require.NoError(t, testStore.WithOrgTx(testCtx, org, func(ctx context.Context) error { + return testStore.UpdateDatasourceSourceFetchScope(ctx, &moved) + })) + + reloaded := installationSourceByID(t, source.ID) + require.Equal(t, "release", reloaded.Branch) + require.Equal(t, []string{"docs", "guides"}, reloaded.Paths) + require.Equal(t, []string{".md", ".mdx"}, reloaded.FileExtensions) + require.Equal(t, "public", reloaded.GitHubCredentialKind, + "the config envelope must be rebuilt from the whole source, not from the file filter") + require.Equal(t, template.Repo, reloaded.Repo, "an edit cannot re-aim the source at another repository") + require.Equal(t, source.BoundaryNodeID, reloaded.BoundaryNodeID, "an edit cannot move the collection") + require.Empty(t, reloaded.CredentialSecretRef) + + // Clearing is what an empty field means, exactly as on connect. + cleared := moved + cleared.Branch = "" + cleared.Paths = nil + cleared.FileExtensions = nil + require.NoError(t, testStore.WithOrgTx(testCtx, org, func(ctx context.Context) error { + return testStore.UpdateDatasourceSourceFetchScope(ctx, &cleared) + })) + empty := installationSourceByID(t, source.ID) + require.Empty(t, empty.Branch) + require.Empty(t, empty.Paths) + require.Empty(t, empty.FileExtensions) +} + +// The statement's own answer to "was it there?". UpdateSource reads the source +// to validate against in one transaction and writes it in another, so under +// READ COMMITTED the row can be deleted in between and the UPDATE simply +// matches nothing. Reported as not-found, because the caller commits an audit +// record and queues a reconcile on the strength of this returning nil. +func TestPostgresUpdateDatasourceSourceFetchScopeReportsAMissingRow(t *testing.T) { + owner := seedUser(t) + org := seedOrg(t, owner) + source := installationSourceByID(t, seedDatasourceSource(t, org)) + + gone := *source + gone.ID = business.NewIDString() + gone.Branch = "release" + err := testStore.WithOrgTx(testCtx, org, func(ctx context.Context) error { + return testStore.UpdateDatasourceSourceFetchScope(ctx, &gone) + }) + require.ErrorIs(t, err, business.ErrDatasourceSourceNotFound, + "an UPDATE that matched no row must not read as a successful edit") + + // Another organization's id is equally no row of this one's, and the tenant + // policy is what makes it so — the same empty result, not a silent write. + foreign := *source + foreign.OrgID = seedOrg(t, seedUser(t)) + foreign.Branch = "release" + err = testStore.WithOrgTx(testCtx, foreign.OrgID, func(ctx context.Context) error { + return testStore.UpdateDatasourceSourceFetchScope(ctx, &foreign) + }) + require.ErrorIs(t, err, business.ErrDatasourceSourceNotFound) + + reloaded := installationSourceByID(t, source.ID) + require.Equal(t, source.Branch, reloaded.Branch, "neither refusal may have written") +} + +// The locked read is what an edit compares its expectation against, so it has +// to return the row as stored — including the file-type filter that lives in the +// config envelope rather than a column of its own — and nothing for a row that +// is not there. +func TestPostgresLockDatasourceSourceFetchScopeReadsTheStoredScope(t *testing.T) { + owner := seedUser(t) + org := seedOrg(t, owner) + template := installationSourceByID(t, seedDatasourceSource(t, org)) + + source := *template + source.ID = business.NewIDString() + source.Branch = "main" + source.Paths = []string{"docs"} + source.FileExtensions = []string{".md"} + require.NoError(t, testStore.WithOrgTx(testCtx, org, func(ctx context.Context) error { + return testStore.InsertDatasourceSource(ctx, &source) + })) + + require.NoError(t, testStore.WithOrgTx(testCtx, org, func(ctx context.Context) error { + locked, err := testStore.LockDatasourceSourceFetchScope(ctx, org, source.ID) + if err != nil { + return err + } + require.NotNil(t, locked) + require.Equal(t, "main", locked.Branch) + require.Equal(t, []string{"docs"}, locked.Paths) + require.Equal(t, []string{".md"}, locked.FileExtensions) + + missing, err := testStore.LockDatasourceSourceFetchScope(ctx, org, business.NewIDString()) + if err != nil { + return err + } + require.Nil(t, missing, "a row that is not there must read as absent, not as an empty scope") + return nil + })) +} diff --git a/module/services/accounts/code/pkg/infra/postgres_datasource_sources.go b/module/services/accounts/code/pkg/infra/postgres_datasource_sources.go index 30dc690a3..b08e1458c 100644 --- a/module/services/accounts/code/pkg/infra/postgres_datasource_sources.go +++ b/module/services/accounts/code/pkg/infra/postgres_datasource_sources.go @@ -98,13 +98,16 @@ func scanDatasourceSourceInto(row pgx.Row, label *string) (*business.DatasourceS return &d, nil } -// InsertDatasourceSource writes a new connected Source. Runs under the caller's -// WithOrgTx. -func (s *PostgresStore) InsertDatasourceSource(ctx context.Context, source *business.DatasourceSource) error { - paths := source.Paths - if paths == nil { - paths = []string{} - } +// datasourceConfigJSON encodes a Source's provider-specific non-secret config +// for the `config` column, or nil when the provider stores none. +// +// Shared by the insert and the in-place fetch-scope update so one encoding +// decides what the column holds. For a github source the envelope carries the +// credential kind as well as the file-type filter, and an update that rebuilt +// the JSON from the filter alone would drop the marker that says the source +// holds no credential — turning a public source into one whose every fetch +// looks for an envelope that was never there. +func datasourceConfigJSON(source *business.DatasourceSource) ([]byte, error) { var payload any switch { case source.Provider == business.DatasourceProviderGitHub: @@ -116,15 +119,95 @@ func (s *PostgresStore) InsertDatasourceSource(ctx context.Context, source *busi case source.Upload != nil: payload = source.Upload } - var config []byte - if payload != nil { - encoded, err := json.Marshal(payload) - if err != nil { - return err - } - config = encoded + if payload == nil { + return nil, nil } - _, err := s.getQueryExecutor(ctx).Exec(ctx, ` + return json.Marshal(payload) +} + +// UpdateDatasourceSourceFetchScope replaces what a Source fetches — its branch, +// its path prefixes and the config envelope carrying its file-type filter — +// from the already-validated Source the caller holds. Nothing else is written: +// the repository, the provider, the boundary and the credential envelope are +// not in the statement, so this cannot re-aim a source at another repository or +// another collection however it is called. +// +// No migration is owed for it: the three columns, app_tenant's UPDATE +// privilege and the tenant policy's WITH CHECK on org_id all already exist, and +// the statement never touches org_id, so the row it may write is the row the +// policy already admits. The baseline's `datasource_sources_source_read_revision` +// trigger covers branch and paths (not config) and bumps the org's +// AUTHORIZATION revision — the counter `CheckAuthorizationRevision` compares. +// It is not a hand-off signal and nothing downstream reads it as one: what tells +// the consuming module its snapshot moved is the reconcile UpdateSource enqueues. +// +// Matching no row is reported as business.ErrDatasourceSourceNotFound rather +// than as a silent success. The caller read the source in an earlier statement, +// so under this store's READ COMMITTED transactions the row can have been +// deleted in between; swallowing the empty command tag would commit an audit +// record and queue a sync for a source that no longer exists, and answer the +// caller as though the edit had landed. Runs under the caller's WithOrgTx. +func (s *PostgresStore) UpdateDatasourceSourceFetchScope(ctx context.Context, source *business.DatasourceSource) error { + paths := source.Paths + if paths == nil { + paths = []string{} + } + config, err := datasourceConfigJSON(source) + if err != nil { + return err + } + tag, err := s.getQueryExecutor(ctx).Exec(ctx, ` + UPDATE datasource_sources + SET branch = NULLIF($3, ''), paths = $4, config = $5, updated_at = NOW() + WHERE org_id = $1 AND id = $2`, + source.OrgID, source.ID, source.Branch, paths, config) + if err != nil { + return err + } + if tag.RowsAffected() == 0 { + return business.ErrDatasourceSourceNotFound + } + return nil +} + +// LockDatasourceSourceFetchScope reads one org-scoped Source under a row lock, +// or (nil, nil) when none matches. Runs under the caller's WithOrgTx, and the +// lock is held until that transaction ends. +// +// It exists so an edit decides against the row as it is at the moment it +// writes. UpdateSource reads the source once to validate against (a GitHub call +// no transaction should be held open across), and the stored scope can move +// between that read and the write; comparing the caller's expectation against +// this locked read is what makes "replace the scope I was shown" true rather +// than "replace whatever is there now". It takes the plain projection without +// the boundary label, because the label comes from a join that has no row to +// lock. +func (s *PostgresStore) LockDatasourceSourceFetchScope(ctx context.Context, orgID, id string) (*business.DatasourceSource, error) { + row := s.getQueryExecutor(ctx).QueryRow(ctx, + `SELECT `+datasourceSourceColumns+` + FROM datasource_sources WHERE org_id = $1 AND id = $2 FOR UPDATE`, orgID, id) + source, err := scanDatasourceSource(row) + if err == pgx.ErrNoRows { + return nil, nil + } + if err != nil { + return nil, err + } + return source, nil +} + +// InsertDatasourceSource writes a new connected Source. Runs under the caller's +// WithOrgTx. +func (s *PostgresStore) InsertDatasourceSource(ctx context.Context, source *business.DatasourceSource) error { + paths := source.Paths + if paths == nil { + paths = []string{} + } + config, err := datasourceConfigJSON(source) + if err != nil { + return err + } + _, err = s.getQueryExecutor(ctx).Exec(ctx, ` INSERT INTO datasource_sources ( id, org_id, provider, repo, paths, branch, boundary_node_id, credential_secret_ref, webhook_secret_ref, status, config, diff --git a/module/services/accounts/generated/api-contract-surfaces.json b/module/services/accounts/generated/api-contract-surfaces.json index 8b130fd16..4b33a0d58 100644 --- a/module/services/accounts/generated/api-contract-surfaces.json +++ b/module/services/accounts/generated/api-contract-surfaces.json @@ -172,6 +172,7 @@ "/saas.accounts.v1.DatasourceService/RevokeSourceDelegation", "/saas.accounts.v1.DatasourceService/SyncSource", "/saas.accounts.v1.DatasourceService/UnbindDatasourceGroup", + "/saas.accounts.v1.DatasourceService/UpdateSource", "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain" ] }, diff --git a/module/services/accounts/generated/authz-methods.json b/module/services/accounts/generated/authz-methods.json index fe4ecfed5..2f0ea79fb 100644 --- a/module/services/accounts/generated/authz-methods.json +++ b/module/services/accounts/generated/authz-methods.json @@ -2963,6 +2963,51 @@ "policy_sha256": "d658d313d1ed64a0e5493f089c8c2339c8e4c4bc714e3a10c4cd2ee963c2d3e7", "rate_limit_backend_failure_mode": "RATE_LIMIT_BACKEND_FAILURE_MODE_FAIL_OPEN" }, + { + "procedure": "/saas.accounts.v1.DatasourceService/UpdateSource", + "full_service": "saas.accounts.v1.DatasourceService", + "method": "UpdateSource", + "input_type": "saas.accounts.v1.UpdateSourceRequest", + "output_type": "saas.accounts.v1.UpdateSourceResponse", + "client_streaming": false, + "server_streaming": false, + "policy": { + "exposure": "EXPOSURE_AUTHENTICATED", + "tenant": "TENANT_REQUIREMENT_ORG_ADMIN", + "permissions": [], + "scopes": [], + "resource_bindings": [ + { + "request_field": "org_id", + "target": "RESOURCE_TARGET_ORGANIZATION", + "lookup": "RESOURCE_LOOKUP_DIRECT_ID" + } + ], + "mfa": "MFA_REQUIREMENT_NONE", + "audit": { + "events": [ + "saas.datasource.source.updated" + ], + "emission": "AUDIT_EMISSION_SUCCESS" + }, + "idempotency": "IDEMPOTENCY_REQUIREMENT_FORBIDDEN", + "rate_limit": "RATE_LIMIT_CLASS_STANDARD_WRITE", + "request_sensitivity": "SENSITIVITY_INTERNAL", + "response_sensitivity": "SENSITIVITY_CONFIDENTIAL", + "platform_role": "PLATFORM_ROLE_REQUIREMENT_NONE", + "authentication_factor_attempt": false, + "conditions": [], + "impersonation": "IMPERSONATION_REQUIREMENT_UNSPECIFIED" + }, + "policy_tier": "org_admin", + "owner": { + "module": "saas-starter", + "service": "accounts" + }, + "source_proto": "saas/accounts/v1/datasource.proto", + "policy_sha256": "8f18fbf423e4a4f0afa902be617d8b960ec24a684774367482aead685b1b19b0", + "rate_limit_backend_failure_mode": "RATE_LIMIT_BACKEND_FAILURE_MODE_FAIL_OPEN" + }, { "procedure": "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain", "full_service": "saas.accounts.v1.DatasourceService", diff --git a/module/services/accounts/generated/gateway-routes.json b/module/services/accounts/generated/gateway-routes.json index 70a35eb51..2894ef4dc 100644 --- a/module/services/accounts/generated/gateway-routes.json +++ b/module/services/accounts/generated/gateway-routes.json @@ -1057,6 +1057,22 @@ "rewrite_path": "/saas.accounts.v1.DatasourceService/UnbindDatasourceGroup", "remove_after": "2026-10-11" }, + { + "protocol": "GATEWAY_PROTOCOL_CONNECT", + "method": "POST", + "path": "/customers.DatasourceService/UpdateSource", + "match": "GATEWAY_MATCH_EXACT", + "procedure": "/saas.accounts.v1.DatasourceService/UpdateSource", + "owner": { + "module": "saas-starter", + "service": "accounts" + }, + "upstream_endpoint": "connect", + "exposure": "EXPOSURE_AUTHENTICATED", + "source": "GATEWAY_ROUTE_SOURCE_COMPATIBILITY_ALIAS", + "rewrite_path": "/saas.accounts.v1.DatasourceService/UpdateSource", + "remove_after": "2026-10-11" + }, { "protocol": "GATEWAY_PROTOCOL_CONNECT", "method": "POST", @@ -4497,6 +4513,22 @@ "rewrite_path": "", "remove_after": "" }, + { + "protocol": "GATEWAY_PROTOCOL_CONNECT", + "method": "POST", + "path": "/saas.accounts.v1.DatasourceService/UpdateSource", + "match": "GATEWAY_MATCH_EXACT", + "procedure": "/saas.accounts.v1.DatasourceService/UpdateSource", + "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", diff --git a/module/services/accounts/generated/service-catalog.json b/module/services/accounts/generated/service-catalog.json index a4ae49711..142c0b4c8 100644 --- a/module/services/accounts/generated/service-catalog.json +++ b/module/services/accounts/generated/service-catalog.json @@ -133,6 +133,7 @@ "/saas.accounts.v1.DatasourceService/RevokeSourceDelegation", "/saas.accounts.v1.DatasourceService/SyncSource", "/saas.accounts.v1.DatasourceService/UnbindDatasourceGroup", + "/saas.accounts.v1.DatasourceService/UpdateSource", "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain" ] }, @@ -4116,6 +4117,57 @@ "service": "accounts" } }, + { + "procedure": "/saas.accounts.v1.DatasourceService/UpdateSource", + "service": "DatasourceService", + "full_service": "saas.accounts.v1.DatasourceService", + "method": "UpdateSource", + "input_type": "saas.accounts.v1.UpdateSourceRequest", + "output_type": "saas.accounts.v1.UpdateSourceResponse", + "client_streaming": false, + "server_streaming": false, + "deprecated": false, + "source_proto": "saas/accounts/v1/datasource.proto", + "protocols": [ + "PROTOCOL_GRPC", + "PROTOCOL_CONNECT" + ], + "http_bindings": [], + "policy": { + "exposure": "EXPOSURE_AUTHENTICATED", + "tenant": "TENANT_REQUIREMENT_ORG_ADMIN", + "permissions": [], + "scopes": [], + "resource_bindings": [ + { + "request_field": "org_id", + "target": "RESOURCE_TARGET_ORGANIZATION", + "lookup": "RESOURCE_LOOKUP_DIRECT_ID" + } + ], + "mfa": "MFA_REQUIREMENT_NONE", + "audit": { + "events": [ + "saas.datasource.source.updated" + ], + "emission": "AUDIT_EMISSION_SUCCESS" + }, + "idempotency": "IDEMPOTENCY_REQUIREMENT_FORBIDDEN", + "rate_limit": "RATE_LIMIT_CLASS_STANDARD_WRITE", + "request_sensitivity": "SENSITIVITY_INTERNAL", + "response_sensitivity": "SENSITIVITY_CONFIDENTIAL", + "platform_role": "PLATFORM_ROLE_REQUIREMENT_NONE", + "authentication_factor_attempt": false, + "conditions": [], + "impersonation": "IMPERSONATION_REQUIREMENT_UNSPECIFIED" + }, + "policy_tier": "org_admin", + "description": "Change what a connected source fetches — its branch and path and file-type filters — keeping its credential, history and collection.", + "owner": { + "module": "saas-starter", + "service": "accounts" + } + }, { "procedure": "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain", "service": "DatasourceService", diff --git a/module/services/accounts/proto/saas/accounts/v1/datasource.proto b/module/services/accounts/proto/saas/accounts/v1/datasource.proto index 5b952cf9d..8a75033fc 100644 --- a/module/services/accounts/proto/saas/accounts/v1/datasource.proto +++ b/module/services/accounts/proto/saas/accounts/v1/datasource.proto @@ -378,6 +378,107 @@ message AddSourceResponse { Datasource datasource = 1; } +// UpdateSourceRequest changes what an already-connected source fetches, in +// place: its branch and its path and file-type filters. Nothing else about a +// source is mutable here — the repository, the provider, the collection it +// writes into and its credential are each the identity of something a consumer +// already holds, and changing one of those is a different source. +// +// Every field is a WHOLE-FIELD REPLACEMENT, not a patch: the source is left +// fetching exactly what this request names, so an empty `paths` ingests the +// whole repository and an empty `file_extensions` admits every file type, +// exactly as they mean on connect. A repeated proto3 field cannot tell "not +// sent" from "sent empty", so a request that meant to leave a filter alone +// must repeat it; naming that here is what keeps a clearing from reading as an +// omission, and an omission from being silently preserved. +// +// Because every field is a whole-field replacement, every request asserts the +// source's complete scope — so one is accepted only against the scope its +// author was actually shown. `expected` carries that scope back, and a request +// whose expectation no longer matches the stored row is REFUSED rather than +// applied: without it, a form opened while the source read one way and saved +// after someone else narrowed it would silently restore the wider scope and +// re-ingest everything the other person had just excluded. +message UpdateSourceRequest { + string org_id = 1 [(buf.validate.field).string.uuid = true]; + string id = 2 [(buf.validate.field).string.uuid = true]; + // Git ref (branch) to pull. Empty resolves to the repository default branch, + // as on connect. A branch the source's own credential cannot read is refused + // before anything is written. + string branch = 3 [(buf.validate.field).string.max_len = 255]; + // Repository-relative path prefixes to ingest. Empty ingests the whole repo. + repeated string paths = 4 [(buf.validate.field).repeated = { + max_items: 64 + items: { + string: { + min_len: 1 + max_len: 512 + } + } + }]; + // Case-insensitive file suffixes, e.g. [".md", ".mdx"]. Empty admits every + // file type within paths. + repeated string file_extensions = 5 [(buf.validate.field).repeated = { + max_items: 32 + items: { + string: { + min_len: 2 + max_len: 33 + pattern: "^\\.[A-Za-z0-9][A-Za-z0-9._-]*$" + } + } + }]; + // The scope the caller was shown, as it read it. Required: an unconditional + // whole-field replacement is exactly how one editor's save erases another's, + // so there is no "just apply it" mode to fall back to. + // + // It is the scope and not the row's `updated_at` because `updated_at` moves + // on every successful sync, which would refuse a perfectly good edit for the + // sole reason that the source had reconciled since the form was opened. What + // an editor needs guarded is what they were looking at. + SourceFetchScope expected = 6 [(buf.validate.field).required = true]; +} + +// SourceFetchScope is what a source fetches — the three values UpdateSource +// replaces — carried on its own so a request can state the scope it expects to +// be replacing as well as the one it wants. +// +// Its bounds are deliberately looser than the mutable fields': an expectation +// is only ever compared against the stored row, never stored, so holding it to +// today's shape would make a source that predates that shape impossible to +// edit at all. Size is still bounded, because the comparison reads the whole +// thing. +message SourceFetchScope { + string branch = 1 [(buf.validate.field).string.max_len = 255]; + repeated string paths = 2 [(buf.validate.field).repeated = { + max_items: 64 + items: { + string: { + min_len: 1 + max_len: 512 + } + } + }]; + repeated string file_extensions = 3 [(buf.validate.field).repeated = { + max_items: 32 + items: { + string: { + min_len: 1 + max_len: 33 + } + } + }]; +} + +message UpdateSourceResponse { + Datasource datasource = 1; + // The sync enqueued because the update moved what the source fetches, which + // for a consumer is a new snapshot of the collection. Empty when the request + // changed nothing, which is the only case where no sync runs: every mutable + // field here selects content, so any real change re-snapshots the source. + string job_id = 2; +} + // DatasourceConfigField describes one non-secret config input a provider needs, // so the UI can render the connect form from the catalog rather than hardcoding // it per provider. @@ -610,6 +711,11 @@ enum SourceSyncTrigger { SOURCE_SYNC_TRIGGER_SCHEDULED = 2; // A webhook delivery from the provider. SOURCE_SYNC_TRIGGER_WEBHOOK = 3; + // The source was reconfigured — its branch or its path/file-type filters + // changed — so this sync re-snapshots it at the new scope. It is the one + // trigger that tells a reader the change set is large because the source + // moved rather than because the repository did. + SOURCE_SYNC_TRIGGER_RECONFIGURED = 4; } // SourceSyncChanges counts the change set a sync compiled, relative to the @@ -1577,6 +1683,32 @@ service DatasourceService { platform_role: PLATFORM_ROLE_REQUIREMENT_NONE }; } + // UpdateSource changes what an already-connected source fetches — its branch + // and its path and file-type filters — keeping its identity, its credential, + // its sync history and the collection it writes into. A change that moves + // what is fetched enqueues a sync, whose progress reports the reconfigured + // trigger so a reader can tell a re-snapshot from an ordinary one. + rpc UpdateSource(UpdateSourceRequest) returns (UpdateSourceResponse) { + option (saas.policy.v1.method_policy) = { + exposure: EXPOSURE_AUTHENTICATED + tenant: TENANT_REQUIREMENT_ORG_ADMIN + resource_bindings: { + request_field: "org_id" + target: RESOURCE_TARGET_ORGANIZATION + lookup: RESOURCE_LOOKUP_DIRECT_ID + } + mfa: MFA_REQUIREMENT_NONE + audit: { + events: "saas.datasource.source.updated" + emission: AUDIT_EMISSION_SUCCESS + } + idempotency: IDEMPOTENCY_REQUIREMENT_FORBIDDEN + rate_limit: RATE_LIMIT_CLASS_STANDARD_WRITE + request_sensitivity: SENSITIVITY_INTERNAL + response_sensitivity: SENSITIVITY_CONFIDENTIAL + platform_role: PLATFORM_ROLE_REQUIREMENT_NONE + }; + } // GetDatasourceCatalog returns the host's connector registry: every provider, // its connect metadata, whether it conforms to the datasource connector // envelope, and whether it accepts a new source, so a client can render the diff --git a/module/services/auth-gateway/code/authz_catalog_gen.go b/module/services/auth-gateway/code/authz_catalog_gen.go index b8650b8cf..8ded00ad5 100644 --- a/module/services/auth-gateway/code/authz_catalog_gen.go +++ b/module/services/auth-gateway/code/authz_catalog_gen.go @@ -79,6 +79,7 @@ var generatedAuthorizationByProcedure = map[string]generatedAuthorizationMetadat "/saas.accounts.v1.DatasourceService/RevokeSourceDelegation": {exposure: edgeExposureAuthenticated, rateLimitClass: edgeRateLimitClassStandardWrite, rateLimitBackendFailClosed: false, authenticationFactorAttempt: false, policySHA256: "133e20f5ca467f6eea5a31285fd6fb7fc3328e833f41f5caaa152f607f7b7f74"}, "/saas.accounts.v1.DatasourceService/SyncSource": {exposure: edgeExposureAuthenticated, rateLimitClass: edgeRateLimitClassStandardWrite, rateLimitBackendFailClosed: false, authenticationFactorAttempt: false, policySHA256: "6ce0fab18e353fa96ed9a46f9da3c7aed13627454d344d25a02e8bfcb27a4d1a"}, "/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.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"}, diff --git a/module/services/auth-gateway/code/routing_catalog_gen.go b/module/services/auth-gateway/code/routing_catalog_gen.go index dae8fd049..3da80c298 100644 --- a/module/services/auth-gateway/code/routing_catalog_gen.go +++ b/module/services/auth-gateway/code/routing_catalog_gen.go @@ -72,6 +72,7 @@ func generatedCatalogConnectRoutes() []*RouteEntry { {Service: "accounts_connect", Method: "POST", Path: "/customers.DatasourceService/RevokeSourceDelegation", UpstreamPath: "/saas.accounts.v1.DatasourceService/RevokeSourceDelegation", Procedure: "/saas.accounts.v1.DatasourceService/RevokeSourceDelegation"}, {Service: "accounts_connect", Method: "POST", Path: "/customers.DatasourceService/SyncSource", UpstreamPath: "/saas.accounts.v1.DatasourceService/SyncSource", Procedure: "/saas.accounts.v1.DatasourceService/SyncSource"}, {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.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"}, @@ -287,6 +288,7 @@ func generatedCatalogConnectRoutes() []*RouteEntry { {Service: "accounts_connect", Method: "POST", Path: "/saas.accounts.v1.DatasourceService/RevokeSourceDelegation", UpstreamPath: "", Procedure: "/saas.accounts.v1.DatasourceService/RevokeSourceDelegation"}, {Service: "accounts_connect", Method: "POST", Path: "/saas.accounts.v1.DatasourceService/SyncSource", UpstreamPath: "", Procedure: "/saas.accounts.v1.DatasourceService/SyncSource"}, {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.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"}, diff --git a/module/services/frontend/code/package-lock.json b/module/services/frontend/code/package-lock.json index a907f66f5..f2f60f6e9 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.33", + "@codefly-dev/saas-sdk": "0.3.34", "@codefly-dev/saas-ui": "0.12.8", "@codefly-dev/ui": "0.12.8", "@codefly/saas-plugin-manifest": "0.1.2", @@ -15369,7 +15369,7 @@ }, "packages/codefly-ui": { "name": "@codefly-dev/ui", - "version": "0.12.13", + "version": "0.12.14", "license": "Apache-2.0", "dependencies": { "@base-ui/react": "^1.3.0", @@ -15484,7 +15484,7 @@ }, "packages/saas-sdk": { "name": "@codefly-dev/saas-sdk", - "version": "0.3.33", + "version": "0.3.34", "license": "Apache-2.0", "dependencies": { "@bufbuild/protobuf": "^2.11.0", @@ -15518,7 +15518,7 @@ }, "packages/saas-ui": { "name": "@codefly-dev/saas-ui", - "version": "0.12.13", + "version": "0.12.14", "license": "Apache-2.0", "dependencies": { "@opentelemetry/api": "1.9.1", diff --git a/module/services/frontend/code/package.json b/module/services/frontend/code/package.json index fe3203a21..fd7f6eb8c 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.33", + "@codefly-dev/saas-sdk": "0.3.34", "@codefly-dev/saas-ui": "0.12.8", "@codefly-dev/ui": "0.12.8", "@codefly/saas-plugin-manifest": "0.1.2", diff --git a/module/services/frontend/code/packages/codefly-ui/package.json b/module/services/frontend/code/packages/codefly-ui/package.json index 7cd6e72a3..d8de7ff59 100644 --- a/module/services/frontend/code/packages/codefly-ui/package.json +++ b/module/services/frontend/code/packages/codefly-ui/package.json @@ -1,6 +1,6 @@ { "name": "@codefly-dev/ui", - "version": "0.12.13", + "version": "0.12.14", "description": "The shared, versioned Codefly SaaS frontend kit: the plugin host and the tokens-as-data skin mechanism, standalone so the host and Module-Federation remotes import and dedupe one copy.", "repository": { "type": "git", 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 76359147e..363d105db 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": "saas.accounts.v1", "path": "contract.binpb", - "digest": "sha256:5f4f2ee00084591f5f89adab905afc503c4f0284c5952c243dcbf4141f57d082", + "digest": "sha256:373a8f3a22b7968c36dd9faffb9be5ef64213b1de2a532f5e567007bd65caaa7", "services": [ { "name": "APIKeyService", @@ -138,6 +138,7 @@ "/saas.accounts.v1.DatasourceService/RevokeSourceDelegation", "/saas.accounts.v1.DatasourceService/SyncSource", "/saas.accounts.v1.DatasourceService/UnbindDatasourceGroup", + "/saas.accounts.v1.DatasourceService/UpdateSource", "/saas.accounts.v1.DatasourceService/VerifyDatasourceDomain" ] }, 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 0364c84f1a17569895b4952ffda03ea25163ca2a..590c0a8eb1d2d00c9520361083a0d0e6c244e78f 100644 GIT binary patch delta 40376 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 & export const AddSourceResponseSchema: GenMessage = /*@__PURE__*/ messageDesc(file_saas_accounts_v1_datasource, 9); +/** + * UpdateSourceRequest changes what an already-connected source fetches, in + * place: its branch and its path and file-type filters. Nothing else about a + * source is mutable here — the repository, the provider, the collection it + * writes into and its credential are each the identity of something a consumer + * already holds, and changing one of those is a different source. + * + * Every field is a WHOLE-FIELD REPLACEMENT, not a patch: the source is left + * fetching exactly what this request names, so an empty `paths` ingests the + * whole repository and an empty `file_extensions` admits every file type, + * exactly as they mean on connect. A repeated proto3 field cannot tell "not + * sent" from "sent empty", so a request that meant to leave a filter alone + * must repeat it; naming that here is what keeps a clearing from reading as an + * omission, and an omission from being silently preserved. + * + * Because every field is a whole-field replacement, every request asserts the + * source's complete scope — so one is accepted only against the scope its + * author was actually shown. `expected` carries that scope back, and a request + * whose expectation no longer matches the stored row is REFUSED rather than + * applied: without it, a form opened while the source read one way and saved + * after someone else narrowed it would silently restore the wider scope and + * re-ingest everything the other person had just excluded. + * + * @generated from message saas.accounts.v1.UpdateSourceRequest + */ +export type UpdateSourceRequest = Message<"saas.accounts.v1.UpdateSourceRequest"> & { + /** + * @generated from field: string org_id = 1; + */ + orgId: string; + + /** + * @generated from field: string id = 2; + */ + id: string; + + /** + * Git ref (branch) to pull. Empty resolves to the repository default branch, + * as on connect. A branch the source's own credential cannot read is refused + * before anything is written. + * + * @generated from field: string branch = 3; + */ + branch: string; + + /** + * Repository-relative path prefixes to ingest. Empty ingests the whole repo. + * + * @generated from field: repeated string paths = 4; + */ + paths: string[]; + + /** + * Case-insensitive file suffixes, e.g. [".md", ".mdx"]. Empty admits every + * file type within paths. + * + * @generated from field: repeated string file_extensions = 5; + */ + fileExtensions: string[]; + + /** + * The scope the caller was shown, as it read it. Required: an unconditional + * whole-field replacement is exactly how one editor's save erases another's, + * so there is no "just apply it" mode to fall back to. + * + * It is the scope and not the row's `updated_at` because `updated_at` moves + * on every successful sync, which would refuse a perfectly good edit for the + * sole reason that the source had reconciled since the form was opened. What + * an editor needs guarded is what they were looking at. + * + * @generated from field: saas.accounts.v1.SourceFetchScope expected = 6; + */ + expected?: SourceFetchScope; +}; + +/** + * Describes the message saas.accounts.v1.UpdateSourceRequest. + * Use `create(UpdateSourceRequestSchema)` to create a new message. + */ +export const UpdateSourceRequestSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_saas_accounts_v1_datasource, 10); + +/** + * SourceFetchScope is what a source fetches — the three values UpdateSource + * replaces — carried on its own so a request can state the scope it expects to + * be replacing as well as the one it wants. + * + * Its bounds are deliberately looser than the mutable fields': an expectation + * is only ever compared against the stored row, never stored, so holding it to + * today's shape would make a source that predates that shape impossible to + * edit at all. Size is still bounded, because the comparison reads the whole + * thing. + * + * @generated from message saas.accounts.v1.SourceFetchScope + */ +export type SourceFetchScope = Message<"saas.accounts.v1.SourceFetchScope"> & { + /** + * @generated from field: string branch = 1; + */ + branch: string; + + /** + * @generated from field: repeated string paths = 2; + */ + paths: string[]; + + /** + * @generated from field: repeated string file_extensions = 3; + */ + fileExtensions: string[]; +}; + +/** + * Describes the message saas.accounts.v1.SourceFetchScope. + * Use `create(SourceFetchScopeSchema)` to create a new message. + */ +export const SourceFetchScopeSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_saas_accounts_v1_datasource, 11); + +/** + * @generated from message saas.accounts.v1.UpdateSourceResponse + */ +export type UpdateSourceResponse = Message<"saas.accounts.v1.UpdateSourceResponse"> & { + /** + * @generated from field: saas.accounts.v1.Datasource datasource = 1; + */ + datasource?: Datasource; + + /** + * The sync enqueued because the update moved what the source fetches, which + * for a consumer is a new snapshot of the collection. Empty when the request + * changed nothing, which is the only case where no sync runs: every mutable + * field here selects content, so any real change re-snapshots the source. + * + * @generated from field: string job_id = 2; + */ + jobId: string; +}; + +/** + * Describes the message saas.accounts.v1.UpdateSourceResponse. + * Use `create(UpdateSourceResponseSchema)` to create a new message. + */ +export const UpdateSourceResponseSchema: GenMessage = /*@__PURE__*/ + messageDesc(file_saas_accounts_v1_datasource, 12); + /** * DatasourceConfigField describes one non-secret config input a provider needs, * so the UI can render the connect form from the catalog rather than hardcoding @@ -734,7 +880,7 @@ export type DatasourceConfigField = Message<"saas.accounts.v1.DatasourceConfigFi * Use `create(DatasourceConfigFieldSchema)` to create a new message. */ export const DatasourceConfigFieldSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 10); + messageDesc(file_saas_accounts_v1_datasource, 13); /** * DatasourceConnectorBudget is the limits a connector serves within: the size @@ -781,7 +927,7 @@ export type DatasourceConnectorBudget = Message<"saas.accounts.v1.DatasourceConn * Use `create(DatasourceConnectorBudgetSchema)` to create a new message. */ export const DatasourceConnectorBudgetSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 11); + messageDesc(file_saas_accounts_v1_datasource, 14); /** * DatasourceProviderDescriptor is one catalog entry: everything the UI needs to @@ -925,7 +1071,7 @@ export type DatasourceProviderDescriptor = Message<"saas.accounts.v1.DatasourceP * Use `create(DatasourceProviderDescriptorSchema)` to create a new message. */ export const DatasourceProviderDescriptorSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 12); + messageDesc(file_saas_accounts_v1_datasource, 15); /** * @generated from message saas.accounts.v1.GetDatasourceCatalogRequest @@ -938,7 +1084,7 @@ export type GetDatasourceCatalogRequest = Message<"saas.accounts.v1.GetDatasourc * Use `create(GetDatasourceCatalogRequestSchema)` to create a new message. */ export const GetDatasourceCatalogRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 13); + messageDesc(file_saas_accounts_v1_datasource, 16); /** * @generated from message saas.accounts.v1.GetDatasourceCatalogResponse @@ -955,7 +1101,7 @@ export type GetDatasourceCatalogResponse = Message<"saas.accounts.v1.GetDatasour * Use `create(GetDatasourceCatalogResponseSchema)` to create a new message. */ export const GetDatasourceCatalogResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 14); + messageDesc(file_saas_accounts_v1_datasource, 17); /** * @generated from message saas.accounts.v1.ListSourcesRequest @@ -972,7 +1118,7 @@ export type ListSourcesRequest = Message<"saas.accounts.v1.ListSourcesRequest"> * Use `create(ListSourcesRequestSchema)` to create a new message. */ export const ListSourcesRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 15); + messageDesc(file_saas_accounts_v1_datasource, 18); /** * @generated from message saas.accounts.v1.ListSourcesResponse @@ -989,7 +1135,7 @@ export type ListSourcesResponse = Message<"saas.accounts.v1.ListSourcesResponse" * Use `create(ListSourcesResponseSchema)` to create a new message. */ export const ListSourcesResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 16); + messageDesc(file_saas_accounts_v1_datasource, 19); /** * @generated from message saas.accounts.v1.GetSourceRequest @@ -1011,7 +1157,7 @@ export type GetSourceRequest = Message<"saas.accounts.v1.GetSourceRequest"> & { * Use `create(GetSourceRequestSchema)` to create a new message. */ export const GetSourceRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 17); + messageDesc(file_saas_accounts_v1_datasource, 20); /** * @generated from message saas.accounts.v1.GetSourceResponse @@ -1028,7 +1174,7 @@ export type GetSourceResponse = Message<"saas.accounts.v1.GetSourceResponse"> & * Use `create(GetSourceResponseSchema)` to create a new message. */ export const GetSourceResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 18); + messageDesc(file_saas_accounts_v1_datasource, 21); /** * @generated from message saas.accounts.v1.SyncSourceRequest @@ -1071,7 +1217,7 @@ export type SyncSourceRequest = Message<"saas.accounts.v1.SyncSourceRequest"> & * Use `create(SyncSourceRequestSchema)` to create a new message. */ export const SyncSourceRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 19); + messageDesc(file_saas_accounts_v1_datasource, 22); /** * @generated from message saas.accounts.v1.SyncSourceResponse @@ -1091,7 +1237,7 @@ export type SyncSourceResponse = Message<"saas.accounts.v1.SyncSourceResponse"> * Use `create(SyncSourceResponseSchema)` to create a new message. */ export const SyncSourceResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 20); + messageDesc(file_saas_accounts_v1_datasource, 23); /** * @generated from message saas.accounts.v1.GetSourceSyncRequest @@ -1123,7 +1269,7 @@ export type GetSourceSyncRequest = Message<"saas.accounts.v1.GetSourceSyncReques * Use `create(GetSourceSyncRequestSchema)` to create a new message. */ export const GetSourceSyncRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 21); + messageDesc(file_saas_accounts_v1_datasource, 24); /** * @generated from message saas.accounts.v1.SourceSyncDelivery @@ -1150,7 +1296,7 @@ export type SourceSyncDelivery = Message<"saas.accounts.v1.SourceSyncDelivery"> * Use `create(SourceSyncDeliverySchema)` to create a new message. */ export const SourceSyncDeliverySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 22); + messageDesc(file_saas_accounts_v1_datasource, 25); /** * SourceSyncChanges counts the change set a sync compiled, relative to the @@ -1211,7 +1357,7 @@ export type SourceSyncChanges = Message<"saas.accounts.v1.SourceSyncChanges"> & * Use `create(SourceSyncChangesSchema)` to create a new message. */ export const SourceSyncChangesSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 23); + messageDesc(file_saas_accounts_v1_datasource, 26); /** * SourceSyncFailure is why a sync is waiting to retry, or why it failed. @@ -1259,7 +1405,7 @@ export type SourceSyncFailure = Message<"saas.accounts.v1.SourceSyncFailure"> & * Use `create(SourceSyncFailureSchema)` to create a new message. */ export const SourceSyncFailureSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 24); + messageDesc(file_saas_accounts_v1_datasource, 27); /** * SourceSyncProgress is one sync's phases, each stamped from the durable @@ -1346,7 +1492,7 @@ export type SourceSyncProgress = Message<"saas.accounts.v1.SourceSyncProgress"> * Use `create(SourceSyncProgressSchema)` to create a new message. */ export const SourceSyncProgressSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 25); + messageDesc(file_saas_accounts_v1_datasource, 28); /** * @generated from message saas.accounts.v1.GetSourceSyncResponse @@ -1378,7 +1524,7 @@ export type GetSourceSyncResponse = Message<"saas.accounts.v1.GetSourceSyncRespo * Use `create(GetSourceSyncResponseSchema)` to create a new message. */ export const GetSourceSyncResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 26); + messageDesc(file_saas_accounts_v1_datasource, 29); /** * @generated from message saas.accounts.v1.DeleteSourceRequest @@ -1400,7 +1546,7 @@ export type DeleteSourceRequest = Message<"saas.accounts.v1.DeleteSourceRequest" * Use `create(DeleteSourceRequestSchema)` to create a new message. */ export const DeleteSourceRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 27); + messageDesc(file_saas_accounts_v1_datasource, 30); /** * @generated from message saas.accounts.v1.DeleteSourceResponse @@ -1413,7 +1559,7 @@ export type DeleteSourceResponse = Message<"saas.accounts.v1.DeleteSourceRespons * Use `create(DeleteSourceResponseSchema)` to create a new message. */ export const DeleteSourceResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 28); + messageDesc(file_saas_accounts_v1_datasource, 31); /** * GitHubAppRepository is one repository a verified App installation grants this @@ -1451,7 +1597,7 @@ export type GitHubAppRepository = Message<"saas.accounts.v1.GitHubAppRepository" * Use `create(GitHubAppRepositorySchema)` to create a new message. */ export const GitHubAppRepositorySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 29); + messageDesc(file_saas_accounts_v1_datasource, 32); /** * @generated from message saas.accounts.v1.BeginGitHubAppSetupRequest @@ -1468,7 +1614,7 @@ export type BeginGitHubAppSetupRequest = Message<"saas.accounts.v1.BeginGitHubAp * Use `create(BeginGitHubAppSetupRequestSchema)` to create a new message. */ export const BeginGitHubAppSetupRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 30); + messageDesc(file_saas_accounts_v1_datasource, 33); /** * @generated from message saas.accounts.v1.BeginGitHubAppSetupResponse @@ -1504,7 +1650,7 @@ export type BeginGitHubAppSetupResponse = Message<"saas.accounts.v1.BeginGitHubA * Use `create(BeginGitHubAppSetupResponseSchema)` to create a new message. */ export const BeginGitHubAppSetupResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 31); + messageDesc(file_saas_accounts_v1_datasource, 34); /** * @generated from message saas.accounts.v1.CompleteGitHubAppSetupRequest @@ -1552,7 +1698,7 @@ export type CompleteGitHubAppSetupRequest = Message<"saas.accounts.v1.CompleteGi * Use `create(CompleteGitHubAppSetupRequestSchema)` to create a new message. */ export const CompleteGitHubAppSetupRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 32); + messageDesc(file_saas_accounts_v1_datasource, 35); /** * @generated from message saas.accounts.v1.CompleteGitHubAppSetupResponse @@ -1578,7 +1724,7 @@ export type CompleteGitHubAppSetupResponse = Message<"saas.accounts.v1.CompleteG * Use `create(CompleteGitHubAppSetupResponseSchema)` to create a new message. */ export const CompleteGitHubAppSetupResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 33); + messageDesc(file_saas_accounts_v1_datasource, 36); /** * @generated from message saas.accounts.v1.MigrateGitHubSourceToAppRequest @@ -1600,7 +1746,7 @@ export type MigrateGitHubSourceToAppRequest = Message<"saas.accounts.v1.MigrateG * Use `create(MigrateGitHubSourceToAppRequestSchema)` to create a new message. */ export const MigrateGitHubSourceToAppRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 34); + messageDesc(file_saas_accounts_v1_datasource, 37); /** * @generated from message saas.accounts.v1.MigrateGitHubSourceToAppResponse @@ -1617,7 +1763,7 @@ export type MigrateGitHubSourceToAppResponse = Message<"saas.accounts.v1.Migrate * Use `create(MigrateGitHubSourceToAppResponseSchema)` to create a new message. */ export const MigrateGitHubSourceToAppResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 35); + messageDesc(file_saas_accounts_v1_datasource, 38); /** * DatasourceAccountLink is one person's provider account, proven by signing in @@ -1673,7 +1819,7 @@ export type DatasourceAccountLink = Message<"saas.accounts.v1.DatasourceAccountL * Use `create(DatasourceAccountLinkSchema)` to create a new message. */ export const DatasourceAccountLinkSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 36); + messageDesc(file_saas_accounts_v1_datasource, 39); /** * DatasourceGroupBinding maps one provider group onto one host team, by an @@ -1725,7 +1871,7 @@ export type DatasourceGroupBinding = Message<"saas.accounts.v1.DatasourceGroupBi * Use `create(DatasourceGroupBindingSchema)` to create a new message. */ export const DatasourceGroupBindingSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 37); + messageDesc(file_saas_accounts_v1_datasource, 40); /** * DatasourceVerifiedDomain is a domain an administrator claimed for the tenant. @@ -1787,7 +1933,7 @@ export type DatasourceVerifiedDomain = Message<"saas.accounts.v1.DatasourceVerif * Use `create(DatasourceVerifiedDomainSchema)` to create a new message. */ export const DatasourceVerifiedDomainSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 38); + messageDesc(file_saas_accounts_v1_datasource, 41); /** * DatasourceDirectoryTeam is a host team an administrator may bind a group to. @@ -1811,7 +1957,7 @@ export type DatasourceDirectoryTeam = Message<"saas.accounts.v1.DatasourceDirect * Use `create(DatasourceDirectoryTeamSchema)` to create a new message. */ export const DatasourceDirectoryTeamSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 39); + messageDesc(file_saas_accounts_v1_datasource, 42); /** * @generated from message saas.accounts.v1.BeginDatasourceAccountLinkRequest @@ -1848,7 +1994,7 @@ export type BeginDatasourceAccountLinkRequest = Message<"saas.accounts.v1.BeginD * Use `create(BeginDatasourceAccountLinkRequestSchema)` to create a new message. */ export const BeginDatasourceAccountLinkRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 40); + messageDesc(file_saas_accounts_v1_datasource, 43); /** * @generated from message saas.accounts.v1.BeginDatasourceAccountLinkResponse @@ -1880,7 +2026,7 @@ export type BeginDatasourceAccountLinkResponse = Message<"saas.accounts.v1.Begin * Use `create(BeginDatasourceAccountLinkResponseSchema)` to create a new message. */ export const BeginDatasourceAccountLinkResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 41); + messageDesc(file_saas_accounts_v1_datasource, 44); /** * @generated from message saas.accounts.v1.CompleteDatasourceAccountLinkRequest @@ -1907,7 +2053,7 @@ export type CompleteDatasourceAccountLinkRequest = Message<"saas.accounts.v1.Com * Use `create(CompleteDatasourceAccountLinkRequestSchema)` to create a new message. */ export const CompleteDatasourceAccountLinkRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 42); + messageDesc(file_saas_accounts_v1_datasource, 45); /** * @generated from message saas.accounts.v1.CompleteDatasourceAccountLinkResponse @@ -1924,7 +2070,7 @@ export type CompleteDatasourceAccountLinkResponse = Message<"saas.accounts.v1.Co * Use `create(CompleteDatasourceAccountLinkResponseSchema)` to create a new message. */ export const CompleteDatasourceAccountLinkResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 43); + messageDesc(file_saas_accounts_v1_datasource, 46); /** * @generated from message saas.accounts.v1.ListMyDatasourceAccountLinksRequest @@ -1941,7 +2087,7 @@ export type ListMyDatasourceAccountLinksRequest = Message<"saas.accounts.v1.List * Use `create(ListMyDatasourceAccountLinksRequestSchema)` to create a new message. */ export const ListMyDatasourceAccountLinksRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 44); + messageDesc(file_saas_accounts_v1_datasource, 47); /** * @generated from message saas.accounts.v1.ListMyDatasourceAccountLinksResponse @@ -1958,7 +2104,7 @@ export type ListMyDatasourceAccountLinksResponse = Message<"saas.accounts.v1.Lis * Use `create(ListMyDatasourceAccountLinksResponseSchema)` to create a new message. */ export const ListMyDatasourceAccountLinksResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 45); + messageDesc(file_saas_accounts_v1_datasource, 48); /** * @generated from message saas.accounts.v1.DeleteDatasourceAccountLinkRequest @@ -1980,7 +2126,7 @@ export type DeleteDatasourceAccountLinkRequest = Message<"saas.accounts.v1.Delet * Use `create(DeleteDatasourceAccountLinkRequestSchema)` to create a new message. */ export const DeleteDatasourceAccountLinkRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 46); + messageDesc(file_saas_accounts_v1_datasource, 49); /** * @generated from message saas.accounts.v1.DeleteDatasourceAccountLinkResponse @@ -1993,7 +2139,7 @@ export type DeleteDatasourceAccountLinkResponse = Message<"saas.accounts.v1.Dele * Use `create(DeleteDatasourceAccountLinkResponseSchema)` to create a new message. */ export const DeleteDatasourceAccountLinkResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 47); + messageDesc(file_saas_accounts_v1_datasource, 50); /** * @generated from message saas.accounts.v1.GetDatasourceDirectoryRequest @@ -2010,7 +2156,7 @@ export type GetDatasourceDirectoryRequest = Message<"saas.accounts.v1.GetDatasou * Use `create(GetDatasourceDirectoryRequestSchema)` to create a new message. */ export const GetDatasourceDirectoryRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 48); + messageDesc(file_saas_accounts_v1_datasource, 51); /** * @generated from message saas.accounts.v1.GetDatasourceDirectoryResponse @@ -2044,7 +2190,7 @@ export type GetDatasourceDirectoryResponse = Message<"saas.accounts.v1.GetDataso * Use `create(GetDatasourceDirectoryResponseSchema)` to create a new message. */ export const GetDatasourceDirectoryResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 49); + messageDesc(file_saas_accounts_v1_datasource, 52); /** * @generated from message saas.accounts.v1.BindDatasourceGroupRequest @@ -2076,7 +2222,7 @@ export type BindDatasourceGroupRequest = Message<"saas.accounts.v1.BindDatasourc * Use `create(BindDatasourceGroupRequestSchema)` to create a new message. */ export const BindDatasourceGroupRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 50); + messageDesc(file_saas_accounts_v1_datasource, 53); /** * @generated from message saas.accounts.v1.BindDatasourceGroupResponse @@ -2093,7 +2239,7 @@ export type BindDatasourceGroupResponse = Message<"saas.accounts.v1.BindDatasour * Use `create(BindDatasourceGroupResponseSchema)` to create a new message. */ export const BindDatasourceGroupResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 51); + messageDesc(file_saas_accounts_v1_datasource, 54); /** * @generated from message saas.accounts.v1.UnbindDatasourceGroupRequest @@ -2115,7 +2261,7 @@ export type UnbindDatasourceGroupRequest = Message<"saas.accounts.v1.UnbindDatas * Use `create(UnbindDatasourceGroupRequestSchema)` to create a new message. */ export const UnbindDatasourceGroupRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 52); + messageDesc(file_saas_accounts_v1_datasource, 55); /** * @generated from message saas.accounts.v1.UnbindDatasourceGroupResponse @@ -2128,7 +2274,7 @@ export type UnbindDatasourceGroupResponse = Message<"saas.accounts.v1.UnbindData * Use `create(UnbindDatasourceGroupResponseSchema)` to create a new message. */ export const UnbindDatasourceGroupResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 53); + messageDesc(file_saas_accounts_v1_datasource, 56); /** * @generated from message saas.accounts.v1.ClaimDatasourceDomainRequest @@ -2150,7 +2296,7 @@ export type ClaimDatasourceDomainRequest = Message<"saas.accounts.v1.ClaimDataso * Use `create(ClaimDatasourceDomainRequestSchema)` to create a new message. */ export const ClaimDatasourceDomainRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 54); + messageDesc(file_saas_accounts_v1_datasource, 57); /** * @generated from message saas.accounts.v1.ClaimDatasourceDomainResponse @@ -2167,7 +2313,7 @@ export type ClaimDatasourceDomainResponse = Message<"saas.accounts.v1.ClaimDatas * Use `create(ClaimDatasourceDomainResponseSchema)` to create a new message. */ export const ClaimDatasourceDomainResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 55); + messageDesc(file_saas_accounts_v1_datasource, 58); /** * @generated from message saas.accounts.v1.VerifyDatasourceDomainRequest @@ -2189,7 +2335,7 @@ export type VerifyDatasourceDomainRequest = Message<"saas.accounts.v1.VerifyData * Use `create(VerifyDatasourceDomainRequestSchema)` to create a new message. */ export const VerifyDatasourceDomainRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 56); + messageDesc(file_saas_accounts_v1_datasource, 59); /** * @generated from message saas.accounts.v1.VerifyDatasourceDomainResponse @@ -2206,7 +2352,7 @@ export type VerifyDatasourceDomainResponse = Message<"saas.accounts.v1.VerifyDat * Use `create(VerifyDatasourceDomainResponseSchema)` to create a new message. */ export const VerifyDatasourceDomainResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 57); + messageDesc(file_saas_accounts_v1_datasource, 60); /** * @generated from message saas.accounts.v1.DeleteDatasourceDomainRequest @@ -2228,7 +2374,7 @@ export type DeleteDatasourceDomainRequest = Message<"saas.accounts.v1.DeleteData * Use `create(DeleteDatasourceDomainRequestSchema)` to create a new message. */ export const DeleteDatasourceDomainRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 58); + messageDesc(file_saas_accounts_v1_datasource, 61); /** * @generated from message saas.accounts.v1.DeleteDatasourceDomainResponse @@ -2241,7 +2387,7 @@ export type DeleteDatasourceDomainResponse = Message<"saas.accounts.v1.DeleteDat * Use `create(DeleteDatasourceDomainResponseSchema)` to create a new message. */ export const DeleteDatasourceDomainResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 59); + messageDesc(file_saas_accounts_v1_datasource, 62); /** * SourceDelegation records that a person, by connecting a source, delegated @@ -2314,7 +2460,7 @@ export type SourceDelegation = Message<"saas.accounts.v1.SourceDelegation"> & { * Use `create(SourceDelegationSchema)` to create a new message. */ export const SourceDelegationSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 60); + messageDesc(file_saas_accounts_v1_datasource, 63); /** * @generated from message saas.accounts.v1.ListSourceDelegationsRequest @@ -2345,7 +2491,7 @@ export type ListSourceDelegationsRequest = Message<"saas.accounts.v1.ListSourceD * Use `create(ListSourceDelegationsRequestSchema)` to create a new message. */ export const ListSourceDelegationsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 61); + messageDesc(file_saas_accounts_v1_datasource, 64); /** * @generated from message saas.accounts.v1.ListSourceDelegationsResponse @@ -2364,7 +2510,7 @@ export type ListSourceDelegationsResponse = Message<"saas.accounts.v1.ListSource * Use `create(ListSourceDelegationsResponseSchema)` to create a new message. */ export const ListSourceDelegationsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 62); + messageDesc(file_saas_accounts_v1_datasource, 65); /** * @generated from message saas.accounts.v1.RevokeSourceDelegationRequest @@ -2386,7 +2532,7 @@ export type RevokeSourceDelegationRequest = Message<"saas.accounts.v1.RevokeSour * Use `create(RevokeSourceDelegationRequestSchema)` to create a new message. */ export const RevokeSourceDelegationRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 63); + messageDesc(file_saas_accounts_v1_datasource, 66); /** * @generated from message saas.accounts.v1.RevokeSourceDelegationResponse @@ -2403,7 +2549,7 @@ export type RevokeSourceDelegationResponse = Message<"saas.accounts.v1.RevokeSou * Use `create(RevokeSourceDelegationResponseSchema)` to create a new message. */ export const RevokeSourceDelegationResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 64); + messageDesc(file_saas_accounts_v1_datasource, 67); /** * TriggerIdentity is the identity a declared trigger's occurrences run under. @@ -2463,7 +2609,7 @@ export type TriggerIdentity = Message<"saas.accounts.v1.TriggerIdentity"> & { * Use `create(TriggerIdentitySchema)` to create a new message. */ export const TriggerIdentitySchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 65); + messageDesc(file_saas_accounts_v1_datasource, 68); /** * DeclaredTriggerSchedule is the clock a release declares, in the vocabulary @@ -2530,7 +2676,7 @@ export type DeclaredTriggerSchedule = Message<"saas.accounts.v1.DeclaredTriggerS * Use `create(DeclaredTriggerScheduleSchema)` to create a new message. */ export const DeclaredTriggerScheduleSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 66); + messageDesc(file_saas_accounts_v1_datasource, 69); /** * TriggerDelegationReference is one delegation reference as an operator binds @@ -2629,7 +2775,7 @@ export type TriggerDelegationReference = Message<"saas.accounts.v1.TriggerDelega * Use `create(TriggerDelegationReferenceSchema)` to create a new message. */ export const TriggerDelegationReferenceSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 67); + messageDesc(file_saas_accounts_v1_datasource, 70); /** * TriggerChildContext is one child Work Context this host minted against a @@ -2693,7 +2839,7 @@ export type TriggerChildContext = Message<"saas.accounts.v1.TriggerChildContext" * Use `create(TriggerChildContextSchema)` to create a new message. */ export const TriggerChildContextSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 68); + messageDesc(file_saas_accounts_v1_datasource, 71); /** * SourceTriggerDeclaration is the reconcile schedule this host declares for @@ -2768,7 +2914,7 @@ export type SourceTriggerDeclaration = Message<"saas.accounts.v1.SourceTriggerDe * Use `create(SourceTriggerDeclarationSchema)` to create a new message. */ export const SourceTriggerDeclarationSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 69); + messageDesc(file_saas_accounts_v1_datasource, 72); /** * @generated from message saas.accounts.v1.DelegateSourceTriggersRequest @@ -2790,7 +2936,7 @@ export type DelegateSourceTriggersRequest = Message<"saas.accounts.v1.DelegateSo * Use `create(DelegateSourceTriggersRequestSchema)` to create a new message. */ export const DelegateSourceTriggersRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 70); + messageDesc(file_saas_accounts_v1_datasource, 73); /** * @generated from message saas.accounts.v1.DelegateSourceTriggersResponse @@ -2811,7 +2957,7 @@ export type DelegateSourceTriggersResponse = Message<"saas.accounts.v1.DelegateS * Use `create(DelegateSourceTriggersResponseSchema)` to create a new message. */ export const DelegateSourceTriggersResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 71); + messageDesc(file_saas_accounts_v1_datasource, 74); /** * @generated from message saas.accounts.v1.GetSourceTriggerDeclarationRequest @@ -2833,7 +2979,7 @@ export type GetSourceTriggerDeclarationRequest = Message<"saas.accounts.v1.GetSo * Use `create(GetSourceTriggerDeclarationRequestSchema)` to create a new message. */ export const GetSourceTriggerDeclarationRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 72); + messageDesc(file_saas_accounts_v1_datasource, 75); /** * @generated from message saas.accounts.v1.GetSourceTriggerDeclarationResponse @@ -2866,7 +3012,7 @@ export type GetSourceTriggerDeclarationResponse = Message<"saas.accounts.v1.GetS * Use `create(GetSourceTriggerDeclarationResponseSchema)` to create a new message. */ export const GetSourceTriggerDeclarationResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 73); + messageDesc(file_saas_accounts_v1_datasource, 76); /** * @generated from message saas.accounts.v1.ReconcileSourceRequest @@ -2896,7 +3042,7 @@ export type ReconcileSourceRequest = Message<"saas.accounts.v1.ReconcileSourceRe * Use `create(ReconcileSourceRequestSchema)` to create a new message. */ export const ReconcileSourceRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 74); + messageDesc(file_saas_accounts_v1_datasource, 77); /** * @generated from message saas.accounts.v1.ReconcileSourceResponse @@ -2930,7 +3076,7 @@ export type ReconcileSourceResponse = Message<"saas.accounts.v1.ReconcileSourceR * Use `create(ReconcileSourceResponseSchema)` to create a new message. */ export const ReconcileSourceResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 75); + messageDesc(file_saas_accounts_v1_datasource, 78); /** * @generated from message saas.accounts.v1.LookupReconcileSourceRequest @@ -2947,7 +3093,7 @@ export type LookupReconcileSourceRequest = Message<"saas.accounts.v1.LookupRecon * Use `create(LookupReconcileSourceRequestSchema)` to create a new message. */ export const LookupReconcileSourceRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 76); + messageDesc(file_saas_accounts_v1_datasource, 79); /** * DatasourceService is the tenant-facing surface a solution drives (directly or @@ -3013,7 +3159,7 @@ export type SourceOperation = Message<"saas.accounts.v1.SourceOperation"> & { * Use `create(SourceOperationSchema)` to create a new message. */ export const SourceOperationSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 77); + messageDesc(file_saas_accounts_v1_datasource, 80); /** * @generated from enum saas.accounts.v1.SourceOperation.Effect @@ -3039,7 +3185,7 @@ export enum SourceOperation_Effect { * Describes the enum saas.accounts.v1.SourceOperation.Effect. */ export const SourceOperation_EffectSchema: GenEnum = /*@__PURE__*/ - enumDesc(file_saas_accounts_v1_datasource, 77, 0); + enumDesc(file_saas_accounts_v1_datasource, 80, 0); /** * @generated from message saas.accounts.v1.DeclareSourceOperationsRequest @@ -3066,7 +3212,7 @@ export type DeclareSourceOperationsRequest = Message<"saas.accounts.v1.DeclareSo * Use `create(DeclareSourceOperationsRequestSchema)` to create a new message. */ export const DeclareSourceOperationsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 78); + messageDesc(file_saas_accounts_v1_datasource, 81); /** * @generated from message saas.accounts.v1.DeclareSourceOperationsResponse @@ -3083,7 +3229,7 @@ export type DeclareSourceOperationsResponse = Message<"saas.accounts.v1.DeclareS * Use `create(DeclareSourceOperationsResponseSchema)` to create a new message. */ export const DeclareSourceOperationsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 79); + messageDesc(file_saas_accounts_v1_datasource, 82); /** * @generated from message saas.accounts.v1.ListSourceOperationsRequest @@ -3105,7 +3251,7 @@ export type ListSourceOperationsRequest = Message<"saas.accounts.v1.ListSourceOp * Use `create(ListSourceOperationsRequestSchema)` to create a new message. */ export const ListSourceOperationsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 80); + messageDesc(file_saas_accounts_v1_datasource, 83); /** * @generated from message saas.accounts.v1.ListSourceOperationsResponse @@ -3122,7 +3268,7 @@ export type ListSourceOperationsResponse = Message<"saas.accounts.v1.ListSourceO * Use `create(ListSourceOperationsResponseSchema)` to create a new message. */ export const ListSourceOperationsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 81); + messageDesc(file_saas_accounts_v1_datasource, 84); /** * InvokeSourceOperationRequest carries exactly one JSON object in input_json. @@ -3162,7 +3308,7 @@ export type InvokeSourceOperationRequest = Message<"saas.accounts.v1.InvokeSourc * Use `create(InvokeSourceOperationRequestSchema)` to create a new message. */ export const InvokeSourceOperationRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 82); + messageDesc(file_saas_accounts_v1_datasource, 85); /** * SourceOperationReceipt contains only the committed result and safe metadata. @@ -3207,7 +3353,7 @@ export type SourceOperationReceipt = Message<"saas.accounts.v1.SourceOperationRe * Use `create(SourceOperationReceiptSchema)` to create a new message. */ export const SourceOperationReceiptSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 83); + messageDesc(file_saas_accounts_v1_datasource, 86); /** * @generated from message saas.accounts.v1.InvokeSourceOperationResponse @@ -3231,7 +3377,7 @@ export type InvokeSourceOperationResponse = Message<"saas.accounts.v1.InvokeSour * Use `create(InvokeSourceOperationResponseSchema)` to create a new message. */ export const InvokeSourceOperationResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 84); + messageDesc(file_saas_accounts_v1_datasource, 87); /** * Lookup uses the caller's verified tenant and the effect's stored binding. @@ -3251,7 +3397,7 @@ export type LookupInvokeSourceOperationRequest = Message<"saas.accounts.v1.Looku * Use `create(LookupInvokeSourceOperationRequestSchema)` to create a new message. */ export const LookupInvokeSourceOperationRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 85); + messageDesc(file_saas_accounts_v1_datasource, 88); /** * Runtime-scheduled receipt retention; no host timer or queue is created. @@ -3280,7 +3426,7 @@ export type PruneSourceOperationReceiptsRequest = Message<"saas.accounts.v1.Prun * Use `create(PruneSourceOperationReceiptsRequestSchema)` to create a new message. */ export const PruneSourceOperationReceiptsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 86); + messageDesc(file_saas_accounts_v1_datasource, 89); /** * @generated from message saas.accounts.v1.PruneSourceOperationReceiptsResponse @@ -3309,7 +3455,7 @@ export type PruneSourceOperationReceiptsResponse = Message<"saas.accounts.v1.Pru * Use `create(PruneSourceOperationReceiptsResponseSchema)` to create a new message. */ export const PruneSourceOperationReceiptsResponseSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 87); + messageDesc(file_saas_accounts_v1_datasource, 90); /** * @generated from message saas.accounts.v1.LookupPruneSourceOperationReceiptsRequest @@ -3326,7 +3472,7 @@ export type LookupPruneSourceOperationReceiptsRequest = Message<"saas.accounts.v * Use `create(LookupPruneSourceOperationReceiptsRequestSchema)` to create a new message. */ export const LookupPruneSourceOperationReceiptsRequestSchema: GenMessage = /*@__PURE__*/ - messageDesc(file_saas_accounts_v1_datasource, 88); + messageDesc(file_saas_accounts_v1_datasource, 91); /** * DatasourceProvider identifies the external system a datasource connects to. @@ -3841,6 +3987,16 @@ export enum SourceSyncTrigger { * @generated from enum value: SOURCE_SYNC_TRIGGER_WEBHOOK = 3; */ WEBHOOK = 3, + + /** + * The source was reconfigured — its branch or its path/file-type filters + * changed — so this sync re-snapshots it at the new scope. It is the one + * trigger that tells a reader the change set is large because the source + * moved rather than because the repository did. + * + * @generated from enum value: SOURCE_SYNC_TRIGGER_RECONFIGURED = 4; + */ + RECONFIGURED = 4, } /** @@ -4052,6 +4208,20 @@ export const DatasourceService: GenService<{ input: typeof AddSourceRequestSchema; output: typeof AddSourceResponseSchema; }, + /** + * UpdateSource changes what an already-connected source fetches — its branch + * and its path and file-type filters — keeping its identity, its credential, + * its sync history and the collection it writes into. A change that moves + * what is fetched enqueues a sync, whose progress reports the reconfigured + * trigger so a reader can tell a re-snapshot from an ordinary one. + * + * @generated from rpc saas.accounts.v1.DatasourceService.UpdateSource + */ + updateSource: { + methodKind: "unary"; + input: typeof UpdateSourceRequestSchema; + output: typeof UpdateSourceResponseSchema; + }, /** * GetDatasourceCatalog returns the host's connector registry: every provider, * its connect metadata, whether it conforms to the datasource connector diff --git a/module/services/frontend/code/packages/saas-sdk/package.json b/module/services/frontend/code/packages/saas-sdk/package.json index b57d538a2..190b9b4d6 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.33", + "version": "0.3.34", "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-ui/README.md b/module/services/frontend/code/packages/saas-ui/README.md index b461c4368..797c9dce5 100644 --- a/module/services/frontend/code/packages/saas-ui/README.md +++ b/module/services/frontend/code/packages/saas-ui/README.md @@ -386,7 +386,28 @@ const directory = usePrincipalDirectory(); // loading | ready | refused | failed phase names and getting the terminal cases wrong. `` is the card, and `useSourceSync(client, orgId, source)` the read. - Hooks over a `DatasourceClient`: `useListSources`, `useAddGitHubSource`, - `useSyncSource`, `useDeleteSource`, `useAccessibleScopes`. + `useUpdateSource`, `useSyncSource`, `useDeleteSource`, `useAccessibleScopes`. +- ``, behind the row's **Edit**, changes + what a connected source fetches: its branch and its path and file-type + filters, bound by the same schema the connect form uses. It offers the connect + form's *mutable* fields only — the repository and the collection are the + source's identity and the host refuses to move either, so the dialog states + them rather than offering them disabled. Every field is submitted whole, as + `updateSource` replaces rather than patches, which is why the fields arrive + seeded from the source. A client without `updateSource` is offered no Edit at + all. Because every field is a whole-field replacement, `EditSourceInput` + carries `expected` — the scope the dialog was seeded with — and the host + **refuses** a save whose expectation no longer matches the stored row rather + than applying it: a dialog left open across someone else's narrowing would + otherwise restore the wider scope and re-ingest everything they excluded. A + consumer calling `updateSource` itself must send the scope it last read, not a + blank one. The panel keeps the dialog open on that refusal and refetches the + list, so reopening it seeds from the current row. Saving deletes nothing that + was already ingested — the next sync hands off what the new scope covers, and + what becomes of files it no longer covers is the consuming module's rule. The sync the host queues when the scope moves reports the + `reconfigured` trigger, and `describeSync` says so: a re-snapshot hands off + the whole new scope, which beside an ordinary sync reads as a fault unless + something names the cause. ### Connecting through the GitHub App diff --git a/module/services/frontend/code/packages/saas-ui/package.json b/module/services/frontend/code/packages/saas-ui/package.json index 8573c103b..63f26935f 100644 --- a/module/services/frontend/code/packages/saas-ui/package.json +++ b/module/services/frontend/code/packages/saas-ui/package.json @@ -1,6 +1,6 @@ { "name": "@codefly-dev/saas-ui", - "version": "0.12.13", + "version": "0.12.14", "description": "Reusable SaaS-domain frontend components (datasources, …) the portal and solutions both import, built on @codefly-dev/ui primitives and driven by an injected typed client.", "repository": { "type": "git", diff --git a/module/services/frontend/code/packages/saas-ui/src/datasources/__tests__/edit-source.test.tsx b/module/services/frontend/code/packages/saas-ui/src/datasources/__tests__/edit-source.test.tsx new file mode 100644 index 000000000..c716ff243 --- /dev/null +++ b/module/services/frontend/code/packages/saas-ui/src/datasources/__tests__/edit-source.test.tsx @@ -0,0 +1,267 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { + cleanup, + fireEvent, + render, + screen, + waitFor, +} from "@testing-library/react"; +import type { ReactElement } from "react"; +import { afterEach, expect, it, vi } from "vitest"; +import { DatasourcesPanel } from "../datasources-panel.js"; +import { describeSync } from "../sync-progress-model.js"; +import type { + DatasourceClient, + DatasourceView, + SourceSyncView, +} from "../types.js"; + +afterEach(cleanup); + +function renderWithClient(ui: ReactElement) { + const queryClient = new QueryClient({ + defaultOptions: { queries: { retry: false } }, + }); + return render( + {ui}, + ); +} + +const source: DatasourceView = { + id: "ds-1", + orgId: "org-1", + provider: "github", + repo: "acme/handbook", + paths: ["docs/"], + fileExtensions: [".md"], + branch: "main", + boundaryNodeId: "11111111-1111-1111-1111-111111111111", + boundaryLabel: "Handbook", + webhookConfigured: true, + status: "active", + lastSyncedAt: undefined, + createdAt: undefined, +}; + +function fakeClient( + overrides: Partial = {}, +): DatasourceClient { + return { + listSources: vi.fn(async () => [source]), + addGitHubSource: vi.fn(async () => {}), + reconcileSource: vi.fn(async (_orgId, _id, effectId: string) => ({ + effectId, + status: "committed" as const, + jobId: "job-1", + })), + lookupReconcileSource: vi.fn(async () => undefined), + deleteSource: vi.fn(async () => {}), + updateSource: vi.fn(async () => "job-resnapshot"), + ...overrides, + }; +} + +function fieldValue(label: string): string { + return ( + screen.getByLabelText(label) as HTMLInputElement | HTMLTextAreaElement + ).value; +} + +async function openEditDialog(client: DatasourceClient) { + renderWithClient(); + fireEvent.click( + await screen.findByRole("button", { name: /^More actions for / }), + ); + fireEvent.click(await screen.findByRole("menuitem", { name: "Edit" })); +} + +it("seeds the edit dialog from the source and saves the whole scope", async () => { + const client = fakeClient(); + const onSyncEnqueued = vi.fn(); + renderWithClient( + , + ); + fireEvent.click( + await screen.findByRole("button", { name: /^More actions for / }), + ); + fireEvent.click(await screen.findByRole("menuitem", { name: "Edit" })); + + // The fields arrive holding what the source already fetches, so a person + // editing one filter does not have to retype the other two — which, with a + // whole-field replacement, would otherwise clear them. + expect(fieldValue("Branch (optional)")).toBe("main"); + expect(fieldValue("Paths (optional)")).toBe("docs/"); + expect(fieldValue("File types (optional)")).toBe(".md"); + + fireEvent.change(screen.getByLabelText("Branch (optional)"), { + target: { value: "release" }, + }); + fireEvent.change(screen.getByLabelText("Paths (optional)"), { + target: { value: "docs/\nguides/" }, + }); + fireEvent.click(screen.getByRole("button", { name: "Save changes" })); + + await waitFor(() => expect(client.updateSource).toHaveBeenCalledTimes(1)); + expect(client.updateSource).toHaveBeenCalledWith({ + orgId: "org-1", + id: "ds-1", + branch: "release", + paths: ["docs/", "guides/"], + fileExtensions: [".md"], + // The scope the dialog was seeded from, sent back so the host can refuse + // the save if the source no longer holds it. Without it a form left open + // across someone else's change would silently restore the scope it was + // showing. + expected: { branch: "main", paths: ["docs/"], fileExtensions: [".md"] }, + }); + // Editing a source is not connecting or deleting one: its identity, its + // credential and its history stay where they are. + expect(client.addGitHubSource).not.toHaveBeenCalled(); + expect(client.deleteSource).not.toHaveBeenCalled(); + expect((await screen.findByRole("status")).textContent).toContain( + "re-snapshot is queued", + ); + expect(onSyncEnqueued).toHaveBeenCalledExactlyOnceWith("job-resnapshot"); +}); + +// An empty job id is the host saying the request changed nothing. Reporting it +// as a queued sync would leave a reader waiting for a pass that never runs. +it("says nothing changed when the host queued no sync", async () => { + const client = fakeClient({ updateSource: vi.fn(async () => undefined) }); + await openEditDialog(client); + fireEvent.click(screen.getByRole("button", { name: "Save changes" })); + + await waitFor(() => expect(client.updateSource).toHaveBeenCalledTimes(1)); + expect((await screen.findByRole("status")).textContent).toContain( + "Nothing changed", + ); +}); + +// A refused branch is a correctable mistake, so the dialog keeps what was typed +// and shows the host's own sentence. +it("keeps the dialog open and surfaces a refusal", async () => { + const client = fakeClient({ + updateSource: vi.fn(async () => { + throw new Error("GitHub repository or branch was not found"); + }), + }); + await openEditDialog(client); + fireEvent.change(screen.getByLabelText("Branch (optional)"), { + target: { value: "gone" }, + }); + fireEvent.click(screen.getByRole("button", { name: "Save changes" })); + + expect((await screen.findByRole("alert")).textContent).toContain( + "was not found", + ); + expect(fieldValue("Branch (optional)")).toBe("gone"); +}); + +// A client with no updateSource — an older host, or a consumer's own adapter — +// is offered no Edit at all, rather than a dialog whose save cannot be made. +it("offers no Edit to a client that cannot edit a source", async () => { + const client = fakeClient(); + delete client.updateSource; + renderWithClient(); + fireEvent.click( + await screen.findByRole("button", { name: /^More actions for / }), + ); + await screen.findByRole("menuitem", { name: "Delete" }); + expect(screen.queryByRole("menuitem", { name: "Edit" })).toBeNull(); +}); + +// The repository and the collection are the source's identity: the host refuses +// to move either, so the dialog states them rather than offering them. +it("does not offer the repository or the collection as fields", async () => { + await openEditDialog(fakeClient()); + expect(screen.queryByLabelText("Repository")).toBeNull(); + expect(screen.queryByLabelText("Target collection")).toBeNull(); + expect( + screen.getByRole("dialog", { name: "Edit acme/handbook" }).textContent, + ).toContain("its collection Handbook"); +}); + +// The sync a reconfigure starts hands off the whole new scope. Beside an +// ordinary sync's handful of files that reads as a fault unless the progress +// says the source itself moved. +it("states that a reconfigured sync re-snapshots the source", () => { + const base: SourceSyncView = { + jobId: "job-resnapshot", + phase: "done", + trigger: "reconfigured", + queuedAt: "2026-10-20T10:00:00.000Z", + finishedAt: "2026-10-20T10:00:30.000Z", + changes: { + files: 412, + added: 412, + modified: 0, + deleted: 0, + splitKnown: true, + snapshot: true, + commit: "abc1234", + }, + attempt: 1, + maxAttempts: 24, + }; + expect(describeSync(base).detail).toContain("re-snapshots it"); + // An ordinary manual sync says nothing extra: the note is a fact about the + // pass the host reported, not decoration on every snapshot. + expect(describeSync({ ...base, trigger: "manual" }).detail).toBeUndefined(); +}); + +// The one state where a reconfigured sync must NOT claim a re-snapshot: the +// host compiled no change set, which a forced reconcile reaches when it parked +// the source instead of snapshotting it — what happens when a narrowed scope is +// still over the ingest cap. Narrowing paths is the documented recovery for +// exactly that, so this is the reader who most needs to know nothing was handed +// off, and substituting the scope note for that sentence told them the opposite. +it("does not claim a re-snapshot on a reconfigured sync that handed nothing off", () => { + const unchanged: SourceSyncView = { + jobId: "job-resnapshot", + phase: "done", + trigger: "reconfigured", + queuedAt: "2026-10-20T10:00:00.000Z", + finishedAt: "2026-10-20T10:00:30.000Z", + attempt: 1, + maxAttempts: 24, + }; + const report = describeSync(unchanged); + expect(report.state).toBe("unchanged"); + expect(report.detail).toContain("nothing was handed off"); + expect(report.detail).not.toContain("re-snapshots it"); + + // An ordinary sync that handed nothing off still reads as it always did. + expect(describeSync({ ...unchanged, trigger: "manual" }).detail).toBe( + "The source had not changed, so nothing was handed off.", + ); +}); + +// Saving a scope the source no longer holds is refused by the host, and the +// dialog has to survive it: the message tells the person to reopen the form, +// which is only useful if the row behind it has been refreshed first. +it("keeps the dialog open and refreshes the list when the scope moved", async () => { + const listSources = vi.fn(async () => [source]); + const client = fakeClient({ + listSources, + updateSource: vi.fn(async () => { + throw new Error("This source's branch or filters changed after this form was opened"); + }), + }); + await openEditDialog(client); + fireEvent.change(screen.getByLabelText("Branch (optional)"), { + target: { value: "release" }, + }); + fireEvent.click(screen.getByRole("button", { name: "Save changes" })); + + const alert = await screen.findByText(/changed after this form was opened/); + expect(alert).toBeDefined(); + // Still open, still holding what was typed, so nothing has to be retyped. + expect(fieldValue("Branch (optional)")).toBe("release"); + // Refetched, so reopening the dialog seeds from the current row rather than + // re-sending the same stale expectation and being refused again. + await waitFor(() => expect(listSources.mock.calls.length).toBeGreaterThan(1)); +}); diff --git a/module/services/frontend/code/packages/saas-ui/src/datasources/__tests__/gateway.test.tsx b/module/services/frontend/code/packages/saas-ui/src/datasources/__tests__/gateway.test.tsx index 8d0f4ac62..30b6d0778 100644 --- a/module/services/frontend/code/packages/saas-ui/src/datasources/__tests__/gateway.test.tsx +++ b/module/services/frontend/code/packages/saas-ui/src/datasources/__tests__/gateway.test.tsx @@ -158,6 +158,14 @@ describe("createDatasourceClient", () => { lookupReconcileSource: () => client.lookupReconcileSource("effect-1"), reconnectSource: () => client.reconnectSource!("org-1", "ds-1"), getSourceSync: () => client.getSourceSync!("org-1", "ds-1"), + updateSource: () => + client.updateSource!({ + orgId: "org-1", + id: "ds-1", + branch: "release", + paths: ["docs/"], + expected: { branch: "main", paths: [], fileExtensions: [] }, + }), deleteSource: () => client.deleteSource("org-1", "ds-1"), // The stub answers {} to everything; an operation whose answer must // carry a record rejects on it, after the call this test counts. diff --git a/module/services/frontend/code/packages/saas-ui/src/datasources/datasources-panel.tsx b/module/services/frontend/code/packages/saas-ui/src/datasources/datasources-panel.tsx index 9d386f62d..d84e210f1 100644 --- a/module/services/frontend/code/packages/saas-ui/src/datasources/datasources-panel.tsx +++ b/module/services/frontend/code/packages/saas-ui/src/datasources/datasources-panel.tsx @@ -39,6 +39,7 @@ import { } from "../solution/viewer.js"; import { CollectionGrants } from "./collection-access.js"; import { ConnectGitHubForm } from "./connect-github-form.js"; +import { EditSourceForm } from "./edit-source-form.js"; import { messageOf } from "./errors.js"; import { createDatasourceClient, type GatewayBinding } from "./gateway.js"; import { readAppSetupReturn, scrubAppSetupReturn } from "./github-app-setup.js"; @@ -48,9 +49,10 @@ import { useDeleteSource, useListSources, useReconcileSource, + useUpdateSource, } from "./queries.js"; import type { AdmittedReconcilePass } from "./reconcile-passes.js"; -import type { ConnectGitHubValues } from "./schema.js"; +import type { ConnectGitHubValues, EditSourceValues } from "./schema.js"; import { SourceSyncProgress, useSourceSync } from "./sync-progress.js"; import type { AccessibleScopeView, @@ -219,6 +221,7 @@ function DatasourcesPanelView({ const [executionSource, setExecutionSource] = useState( null, ); + const [editing, setEditing] = useState(null); const [reconnecting, setReconnecting] = useState(null); const [reconnectPending, setReconnectPending] = useState(false); const [reconnectError, setReconnectError] = useState(); @@ -226,6 +229,7 @@ function DatasourcesPanelView({ const completeAppSetup = client.completeGitHubAppSetup?.bind(client); const migrateToApp = client.migrateGitHubSourceToApp?.bind(client); const reconnectSource = client.reconnectSource?.bind(client); + const canEditSource = !!client.updateSource; // The return leg, captured once during the first render: the redirect's // parameters are a property of the URL the page loaded with, so the value has @@ -325,6 +329,7 @@ function DatasourcesPanelView({ const addMutation = useAddGitHubSource(client); const reconcileMutation = useReconcileSource(client); const deleteMutation = useDeleteSource(client); + const updateMutation = useUpdateSource(client); const handleConnect = (values: ConnectGitHubValues) => { addMutation.mutate( @@ -492,6 +497,64 @@ function DatasourcesPanelView({ } }; + // Editing a source replaces what it fetches, so the notice reports what the + // host answered: the sync it queued because the scope moved, or that nothing + // changed. The second is not a failure and must not read as one — a person + // who reopened the dialog and saved it unchanged is owed that sentence, not + // a "sync queued" for a sync that never ran. + const handleEdit = async (values: EditSourceValues) => { + if (!editing) return; + const source = editing; + setSyncNotice(null); + setActionError(null); + try { + const jobId = await updateMutation.mutateAsync({ + orgId, + id: source.id, + branch: values.branch ?? "", + paths: parsePaths(values.paths), + fileExtensions: parsePaths(values.fileExtensions).map((value) => + value.toLowerCase(), + ), + // The scope this dialog was seeded from — the same object the form + // read its defaults out of, which is what the person was looking at + // while they typed. The host refuses the save if the source no + // longer holds it, so a dialog left open across someone else's + // change cannot silently undo it. + expected: { + branch: source.branch, + paths: source.paths, + fileExtensions: source.fileExtensions ?? [], + }, + }); + setEditing(null); + setSyncNotice( + jobId + ? { + title: "Sync queued", + body: `${source.repo} now fetches the scope you saved. The re-snapshot is queued; open History for ingestion results.`, + } + : { + title: "Nothing changed", + body: `${source.repo} already fetched exactly that, so no sync was queued.`, + }, + ); + if (jobId) onSyncEnqueued?.(jobId); + await list.refetch(); + } catch { + // The dialog stays open and shows the host's refusal — an unreadable + // branch is a correctable mistake, and closing it would discard what + // the person typed. + // + // The list is refreshed anyway: one of the refusals is "the source + // changed after you opened this", and the remedy the message gives is + // to reopen the form. Reopening it over a stale row would seed the + // same expectation and be refused again, so the row behind the dialog + // has to be current before they can act on what they were told. + await list.refetch(); + } + }; + const handleDelete = (source: DatasourceView) => { const ok = window.confirm( `Delete the data source for ${source.repo}?\n\n` + @@ -692,6 +755,14 @@ function DatasourcesPanelView({ canManage={canManage} onActivity={client.listActivity ? setActivitySource : undefined} onExecution={renderSourceExecution ? setExecutionSource : undefined} + onEdit={ + canEditSource + ? (source) => { + updateMutation.reset(); + setEditing(source); + } + : undefined + } onReconnect={ reconnectSource ? (source) => { @@ -853,6 +924,19 @@ function DatasourcesPanelView({ }} /> )} + {canManage && editing && canEditSource && ( + { + if (!updateMutation.isPending) setEditing(null); + }} + isPending={updateMutation.isPending} + errorMessage={ + updateMutation.isError ? messageOf(updateMutation.error) : undefined + } + /> + )} {canManage && showConnect && ( ; onSync: (source: DatasourceView) => void; onDelete: (source: DatasourceView) => void; + /** + * Opens the edit dialog for a source. Absent when the client cannot edit one, + * which drops the control rather than offering a dialog whose save would be + * refused by a client that has no such call. + */ + onEdit?: (source: DatasourceView) => void; onActivity?: (source: DatasourceView) => void; /** Present only when a consumer handed the panel an execution view. */ onExecution?: (source: DatasourceView) => void; @@ -1220,6 +1311,11 @@ function SourcesTable({ align="end" className="w-auto min-w-44" > + {onEdit && source.provider === "github" && ( + onEdit(source)}> + Edit + + )} {onReconnect && source.provider === "github" && ( onReconnect(source)} diff --git a/module/services/frontend/code/packages/saas-ui/src/datasources/edit-source-form.tsx b/module/services/frontend/code/packages/saas-ui/src/datasources/edit-source-form.tsx new file mode 100644 index 000000000..9adf306a1 --- /dev/null +++ b/module/services/frontend/code/packages/saas-ui/src/datasources/edit-source-form.tsx @@ -0,0 +1,178 @@ +"use client"; + +import { Button, Input, Label, Textarea } from "@codefly-dev/ui/layout"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useId } from "react"; +import { useForm } from "react-hook-form"; +import { fieldErrorClass as errorClass } from "./credential-mode.js"; +import { type EditSourceValues, editSourceSchema } from "./schema.js"; +import type { DatasourceView } from "./types.js"; + +interface EditSourceFormProps { + /** The source as the host last reported it; its values seed the fields. */ + source: DatasourceView; + onSubmit: (values: EditSourceValues) => void; + onCancel: () => void; + isPending: boolean; + errorMessage?: string; +} + +/** + * Changes what an already-connected source fetches: its branch and its path and + * file-type filters, bound by the same schema the connect form uses, so an edit + * can never store a scope connecting would have refused. + * + * It offers exactly the connect form's *mutable* fields, and the repository and + * the collection are shown as the standing facts they are rather than as + * disabled inputs. Both are the identity of something a consumer already holds + * — a source pointed at another repository is a different source, and one + * writing into another collection replaces that collection's contents — so the + * host refuses to move either, and offering them greyed out would read as "not + * yet" rather than "not here". + * + * The fields are seeded from the source and submitted whole, because that is + * what the RPC means: it replaces the three values rather than patching them, + * so a field left as it arrived is what keeps it, and a field cleared clears + * it. + */ +export function EditSourceForm({ + source, + onSubmit, + onCancel, + isPending, + errorMessage, +}: EditSourceFormProps) { + const fieldId = useId(); + const idFor = (name: string) => `${fieldId}-${name}`; + const form = useForm({ + resolver: zodResolver(editSourceSchema), + defaultValues: { + branch: source.branch, + // One prefix per line, as the connect form's field is parsed, so the + // value a person sees is the value they would have typed. + paths: source.paths.join("\n"), + fileExtensions: (source.fileExtensions ?? []).join(", "), + }, + }); + const { errors } = form.formState; + + return ( +
+
+
+

Edit {source.repo}

+

+ Change what this source fetches. Its repository + {source.boundaryLabel + ? `, its collection ${source.boundaryLabel}` + : ""}{" "} + and its credential stay as they are, and so does its sync history. +

+ {/* + * Said separately, and said at all, because narrowing a filter is + * the edit whose consequence is least obvious. Saving deletes + * nothing: the host hands off the files in the new scope and emits + * no removal for the ones that left it, so whether a consuming + * module drops what the new snapshot no longer names is that + * module's rule, not something this host decides or can report. + * Promising that de-scoped content is retained would be a guess, + * and promising it is removed would be a guess in the direction + * someone clearing out files they should not have ingested would + * act on. + */} +

+ Saving does not delete anything that was already ingested. The next + sync hands off what the new scope covers; what becomes of files it + no longer covers is up to whatever reads this collection. +

+
+ +
+
+ + +

+ Changing the branch re-snapshots the source: the consuming module + receives the new branch's files as one change set. +

+ {errors.branch && ( +

+ {errors.branch.message} +

+ )} +
+ +
+ +