Skip to content

Resolve

Intelligence › Resolve (?tab=resolve) shows what the tenant’s registered sources actually contain:

  • how their datasets group into data domains;
  • how the sources are used;
  • whether what a source declares about itself agrees with what its data shows.

It is ported from FACE’s Reconcile and Data Connectors pages, not from Atlas. It reuses FACE’s engine, github.com/org-runink/store/estate (connect, graph, access), rather than reimplementing it.

Resolve is the one place CORE dials a tenant source. The only other dialer is the admin-only connectorsync probe, POST /api/connectors/status/refresh, which covers the connectors CORE publishes and never touches registry records. The agents (datagov included), core connection register and granting a source to the workspace all dial nothing.

Two services, one set of documents

Since 2026-09-26 the page is the shared estate component from org-runink/ui (runink.ui.estate.v1.EstateService). It is mounted by internal/console/atlas_estate.go and drawn by EstatePage in flutter/lib/features/intelligence/views/estate_view.dart.

The older runink.core.atlas.v1.ResolveService (atlas_res.go) stays registered as a deprecated alias for one release. The CapEx pull dialog, the connection wizard’s “Test after saving” and the audit pages still call it. Both services read and write the same three stored documents, so each sees the other’s writes.

EstateServiceResolveService (deprecated)Class
ListSources, GetSourceDescription, GetAccessPatterns, GetEstateMap, GetReconciliationListSources, GetSourceDescription, GetAccessPatterns, GetEstate, GetReconciliationread
TestSource, ExploreSource, ReadAccessPatterns, MapEstatethe same fouradmin-write

Reads never dial

The read RPCs answer from what the last dialing action committed. Nothing dials on a read, a timer or a page load. A test fails the engine if a read calls it.

The page re-reads GetEstate every 5 seconds only while a map run is RUNNING and the tab is on screen.

The four dialing actions

Each is admin-write (CORE_ATLAS_ADMINS). Each is audited under resource atlas-estate with the outcome, including refusals, and each sits behind a confirm dialog on the page that states what it dials and what it keeps.

ActionAudit actionBudget (atlas_res.go)What it does
TestSourceatlas.resolve.test20 sChecks that the source answers with its credentials
ExploreSourceatlas.resolve.explore90 sReads the catalogue: datasets, columns, types, declared keys, view dependencies, and profile statistics where the engine has them
ReadAccessPatternsatlas.resolve.access90 sReads the source’s own access views over a window of window_days 1..90
MapEstateatlas.resolve.map15 min per run, 30 s per sourceBuilds the estate domain map across sources. It returns at once and runs in the background, one run in flight per console

A second map while one is running is refused: map run … started by … at … is still in flight; one run at a time.

Credentials are opened with the registry’s audited Credentials read for one engine call and then dropped. They are never stored, returned on the wire, logged or handed to an agent. Engine errors are scrubbed (resScrub) before they are recorded.

Choosing the runner

Every action, and CapexService.PullFeed, first decides the effective runner (resEffectiveRunner) from the request’s optional runner field:

runner on the requestEffective runner
absent (empty)The connection’s binding. An empty binding means console
consoleRunink managed, explicitly, even for a connection bound to a self-hosted runner
a runner nameThat registered runner

A name must be a DNS label or console. Anything else gets runner … is not a runner name (a DNS label, or "console" for the Runink managed runner).

console dials in-process. The runner registry is not even read. Any other runner is refused until dispatch to runners exists (rollout step 6). No bundle is opened, the engine is not called, and there is no fallback to another runner. The refusal names the state it found:

State foundOutcomeWhat the detail says
Not registeredrunner_unknownEnrol that runner, or choose console
Revokedrunner_revokedWho revoked it and when
Pending (not enrolled yet)runner_offlineEnrolment is not complete
Active, not connectedrunner_offlineCORE has no live connection to its address
Active and connectedrunner_offline“connected; dispatch arrives in rollout step 6”. This never claims the runner is down
Registry unreadablefailed“runner registry unavailable”. Never runner_unknown

In a map run, the runner routes every source, and a refused source becomes a gap in the map. The effective runner, and whether it came from the action or the binding, is recorded in the audit detail and in ActionRecord.runner. Runners themselves are managed on DataEx › Runners.

The values boundary

atlas_res_estate.go is the boundary where values stop. It copies structure and counts field by field into record types that have no field for a cell, MIN/MAX, a sample row, a principal name or a credential. Two tests guard this: TestAtlasResolveEstateAdapterDropsCells and TestAtlasResolveRecordsCarryNoCellShapedField.

KeptNever kept
Datasets, columns, types, nullability, declared keysSampled values, sample rows
Row counts, NULL counts, DISTINCT counts (with their basis)MIN / MAX / mean (a customer cell)
Access counts: reads, writes, distinct principalsPrincipal names
Outcome, time and actor of each actionCredentials

Explore profiling limits. Where the engine supports statistics, Explore computes per-column aggregates over at most 25 datasets (resMaxStatDatasets). Only DISTINCT and NULL counts cross the boundary. Explore also keeps the statistics pass’s counted row count.

No confidence number. Domains carry an evidence class (declared, measured or weak) and every edge carries an evidence kind. A GUESS edge is drawn dotted in the warning tone and is never styled like a declared key.

Storage

The documents are metadoc kinds in the console’s appfs root (atlas_res_store.go):

DocumentContentsBudget
var/lib/atlas-estatePer-source last action, access counts, map runs512 KiB
var/lib/atlas-estate-mapThe last committed map768 KiB
var/lib/atlas-estate-desc-<h>One source’s last Explore, with h = sha256(id)[:16]768 KiB

What Resolve records as data lineage

When an Explore, ReadAccessPatterns or MapEstate commits, the console records what it observed as data lineage (datalineageresolve.go). This happens in process: there is no HTTP hop and no ingest token. The edges go into the same observed-lineage store the apps publish into, under the publisher core-resolve. The app ingest refuses that publisher name from anyone else with a 403.

Edge kindBetweenFrom
DECLARED_FOREIGN_KEYdataset → datasetExplore’s declared keys, and the map’s declared keys
INFERRED_* placementdataset → domainThe map, only for domains whose members come from two or more sources
OBSERVED_ACCESSdataset → principalsAccess counts, only where a stated read or write count is above zero
DECLARED_VIEW_DEPENDENCYbase → viewThe source’s own catalogue: Postgres pg_depend, MySQL VIEW_TABLE_USAGE. The Lineage page draws these as “Declared derivations”

Not recorded: GUESSED_COLUMN_OVERLAP (a guess is not an observation) and catalogue containment.

Each edge carries only qualified names, the kind, counts and provenance. It carries no constraint or column name, no detail text and no admin identity.

Edges are deduplicated source→destination. Each commit replaces its own scope’s claims (explore:<id>, access:<id>, map), and claims expire after 30 days. The Resolve lineage key is capped at 256 edges / 512 KiB, and the weakest edges are dropped first and counted.

A committed map also raises the playbook harness event estate / estate.mapped, with attributes domains, unplaced, gaps, drift, shadow and missing. See Playbooks.

Refusals you will see

  • On a console with no sign-in: this console has no sign-in configured, so dialing a tenant source would be unattributable — set GOOGLE_CLIENT_ID or CONSOLE_ADMIN_PASSWORD before testing, exploring or mapping sources.
  • On the page, the first PermissionDenied disables every dial and shows the console’s reason. A FailedPrecondition or InvalidArgument is drawn verbatim and gates nothing else.

More in Troubleshooting.