Skip to content

datasources: a provider is resolved from a composed service, not a Go import — object storage first #1268

Open

Description

@antoinetoussaint-byte

What we are trying to do

Adding a datasource should not mean editing, compiling and releasing accounts.

pkg/business/datasources.go imports every provider package directly:

"accounts/pkg/datasource/apisource"
"accounts/pkg/datasource/connector"
"accounts/pkg/datasource/crawler"
"accounts/pkg/datasource/github"
"accounts/pkg/datasource/objectstore"

and declares the providers as four constants — github, api, crawler, upload. The connector model itself is already generic: pkg/datasource/connector/ states an eight-clause envelope, Interface is a closed set of five (files, pages, records, messages, events) with the comment "The set is closed: a new interface is a contract change. The set of providers is open", Descriptor carries config fields, credential modes, a readers model, a budget and Conformant with a mandatory Gap, and connectortest/ holds a connector to all of it.

So the model's open set is closed by its host's build. That is the whole of what this issue fixes.

Scope — one PR

Populate the connector registry from composed service endpoints rather than Go imports, and register object storage that way as the first provider.

Size: ~400 hand-written lines. Past ~500, stop and ask rather than finishing.

Ordering

Gated on #1235, which must merge first: it is the proof that a provider can be a composed service, and the content of the three PRs closed as contained in it (#1213, #1216, #1214's successor) lives only on its branch today.

What follows this issue, each its own record when it is reachable, not now:

  1. publish a provider contract so a provider can be written in any language — this is where DatasourceItemReaders and DatasourceProvenance stop being accountsv1 types;
  2. github moves out as a service — the honest test, 4 RPCs but ~1,700 lines of Go behind an app-setup handshake, a push receiver, an OAuth account link and a git client;
  3. only then is "extract a datasource core, keep saas as a thin host" answerable with evidence rather than preference.

Why this shape

The surface is the largest in accounts: datasource.proto answers 34 RPCs, against 26 for authorization.proto, 25 for platform_admin.proto and 13 for organizations.proto. Classified at 74e35f0b:

Group RPCs
Source lifecycle and catalog — generic 10
Callable source operations — generic 6
Directory, domains and groups — organisation identity, not source work 6
GitHub-specific 4
Provider account links 4
Delegation and triggers 4

A host-independent core would be the first 16, under half the surface. Six are not datasource work at all.

Two templates for the rest already run in this platform, so neither is invented here:

  • a provider as a composed service — obin-ai/module-document-store composes xlsx, docx, pdf, libreoffice, parse and object-storage as six separately published agents, each pinned by release in service.codefly.yaml and dialed through declared dependencies with no address written down;
  • one core, thin hosts — codefly-dev/authority's libraries/authority-core/go plus libraries/conformance/go, which feat(accounts): the host interfaces are decided by the shared authority core #1257 made accounts a second host of, re-implementing no rule, with the shared suite a required CI context.

Docs, in the same PR

  • module/services/accounts/AGENTS.md — the datasource section, on how a provider is resolved.
  • pkg/datasource/connector/CONFORMANCE.md — registration through a service rather than a package.
  • module/services/README.md and DEPLOYMENT_TOPOLOGY.md if the service graph changes.

Reuse, and what is deliberately not reused

Reused: the connector envelope, Descriptor, the registry's validation, connectortest/, connector.Budgeted, and #1235's composed-service client. Not reused: nothing new is written that one of these already does — if this PR grows a second mechanism for provider resolution, it is wrong.

Fix or hack

A fix at the owner: the host stops owning the set of providers. No stopgap is expected; label one if it appears.

Context

🤖 Generated with Claude Code

Activity

  1. antoinetoussaint-byte commented on Oct 27, 2026

    @antoinetoussaint-byte
    ContributorAuthor

    Scope correction from the owner (2026-10-27)

    Three constraints landed after this issue was filed. They do not cancel it — the seam below is still exactly right — but they change what it is a step towards, and they rule out the step this issue named as next.

    1. Each deployment defines its own datasources. The provider set is a property of the composition, not of any host's build. So the catalog is derived: GetDatasourceCatalog answers from the provider services this deployment composed and their declared endpoints, the way a cell derives reach from endpointConsumers rather than a written-down list. That is precisely what this issue builds, so its scope stands.

    2. The datasource API cannot be turned into one interface — "some of them stream". Interface is a closed set of five, all bulk-only by clause 8, and its own comment says a new interface is a contract change. Closing the data plane into five shapes is what breaks. So the next step is not "publish a provider contract with the five interfaces and the descriptor", as the Ordering section of this issue says. Strike that. What gets published is the vocabulary — DatasourceItemReaders, DatasourceProvenance, source identity, the refusal types — so a provider written elsewhere can speak about readers and provenance without importing a host's generated package. Nothing about how it serves data.

    3. The implementation does not live in this repository, and common providers here are convenience, not the exhaustive set. saas's job is access control: authorising a source, custody of the credential handed per call, the boundary node and its grants, the per-credential budget across sources and replicas, one audit vocabulary, and the catalog of what this deployment composed. The data plane — listing, diffing, fetching, streaming, watching — belongs to the datasource's own service.

    A source whose API does not fit the envelope does not need a sixth envelope interface. It declares its operations, and this service authorises each invocation, spends the budget, stamps the credential and keeps a receipt without modelling what the operation does — the callable-sources seam that is already 6 of this service's 34 RPCs (DeclareSourceOperations, ListSourceOperations, InvokeSourceOperation, LookupInvokeSourceOperation and the two receipt-pruning operations).

    What this means for the PR

    Unchanged: resolve a provider from a declared, composed service; object storage first; github/api/crawler stay compiled in; credentials per call; budget host-side; ~400 hand-written lines.

    Added: the catalog must be derived from what is composed, not from a list — a deployment that composes no bucket provider has no bucket entry, and a stored source of an absent provider refuses by name.

    Removed from the Ordering section: "publish a provider contract" as step 1 after this. Replaced by publishing the readers/provenance vocabulary. And nothing here should be built so that only envelope-shaped providers can be described — a Descriptor with no way to say "this one is called, not synced", or an AddSource that refuses what it cannot type, re-closes the set through the back door.

    The investigation is updated to match: obin-ai/handbook design/datasource-module.md on branch buckets, which now carries the control-plane / data-plane split, why the core-and-thin-hosts template may be the wrong one here, and the revised order.

    🤖 Generated with Claude Code

  2. antoinetoussaint-byte commented on Oct 29, 2026

    @antoinetoussaint-byte
    ContributorAuthor

    A lazybox agent is working on this. claude (Opus 5.5) started at 2026-10-27 15:09 UTC, last renewed the claim at 2026-10-29 19:25 UTC, and the claim lapses at 2026-10-29 20:25 UTC unless it is renewed again. A claim is an assertion with an expiry, not proof that a process is alive.

    claim detail
    Lazybox-Claim-Device: effacd542b611010656e
    Lazybox-Claim-Session: a26cd4b2b0
    Lazybox-Claim-Workspace: github-codefly-dev-module-saas-starter-1268
    Lazybox-Claim-Agent: claude
    Lazybox-Claim-Model: Opus 5.5
    Lazybox-Claim-Started: 2026-10-27T15:09:46Z
    Lazybox-Claim-Heartbeat: 2026-10-29T19:25:35Z
    Lazybox-Claim-Expires: 2026-10-29T20:25:35Z
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    workingClaimed by a lazybox agent; cleared when the session ends.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions