Overview
Thepalantir_foundry_migration_tools group moves data and pipeline logic from a
Palantir Foundry tenant into Databricks. It never writes to Foundry. Assign it only
to agents that also have the Databricks and repository tools needed for the target
side. For read-only inventory and query operations, use
palantir_foundry_tools
instead.
Every migration needs two saved connections: a palantir_foundry source and a
Databricks target. Credentials resolve through the Connections service. The
Before running it, confirm that the authenticated user can read both connections; see
Prover connection access
for the separation between agent identity and connection access.
Databricks execution steps use the existing databricks_action path, not a second
executor.
Migration workflow
Call the tools in this order for a pipeline migration:foundry_plan_migration— plan pipeline order from Foundry lineage and discover the tenant’s ontologies. Before translating, inspect each pipeline’stranslatableandnot_translatable_reasonfields. A Foundry job spec stays in dependency order but hastranslatable: false; handle it first as its reason directs, by landing its output withfoundry_extract_dataset(copy_derived=true)or porting the job by hand. Translate only the entries markedtrue.foundry_translate_pipeline— generate a PySpark script for one pipeline.foundry_publish_notebook— publish that script into the Databricks workspace as a notebook.foundry_extract_dataset— land inputs in the schemas named by the translation’snext_step; whensource_schemais set, not every input belongs in the same schema.foundry_materialize_ontology— optionally land selected ontology object types.databricks_action(action="run_notebook")— run the published workspace notebook with an explicit, operator-approvedcluster_id.foundry_verify_migration— characterize both sides and submit one Prover proof.
foundry_verify_migration identity, see
Prover guidance.
For a signed-in person, planning also opens the person’s migration project the
first time this space is planned, then resumes it on later plans, including plans
from another chat. Read the returned project block: its goals report progress,
and gaps and banner explain what the plan could not read. When
project.instruction is present, tell the person what is missing and do not report
the migration complete, even if a goal reads Met for the planned scope. A
plan without a signed-in person has no project block. Project scope and recovery
options are described in Planning project behavior.
Planning project behavior
Each person’s project is bound to the Foundry connection and space, not the chat thread. If the response hasproject_candidates, ask which live project to keep
and call the planner again with its project_id; the other live projects for the
space are retired. An empty candidate list means no project is live, so plan again
without project_id. A project_error explains why no project was bound; the
Foundry plan itself is still returned.
After evidence has been recorded, the project’s goal scope is fixed. If a later
plan differs, scope_diff reports the difference: would_add and would_drop
are proposed changes, not applied changes; new_gaps are recorded even though
they cannot be read. On the
person’s explicit request, extend_scope=true applies additions without removing
anything, while new_attempt=true retires the existing project and starts a fresh
one. project_id, extend_scope, and new_attempt are honored only in the
person’s own chat turn. The response’s retired and still_live fields report
whether older projects were retired successfully.
These project operations read and write Genesis’s database after Foundry planning:
one SELECT, plus one transaction and three SELECTs the first time a space is
planned; three SELECTs on a later plan, with one UPDATE and three additional
SELECTs when its scope changes.
Filing copies and translations
In a signed-in person’s chat, a wholefoundry_extract_dataset copy is filed under
the person’s migration project before it starts. A foundry_translate_pipeline
translation is associated with the project and records its destination when the
script is saved. Both tools match the Foundry connection and dataset or pipeline to
the plan’s scope. If no live project covers the item, the call is refused; plan its
space first with foundry_plan_migration, then retry. If several projects cover it,
ask the person to choose from project_candidates and retry with that project’s
project_id in the person’s own chat turn. A supplied project id must name one of
the person’s live projects that covers the item.
The project fixes destination fields as work goes ahead. A copy that disagrees with
the project’s Databricks connection, catalog, or schema is refused and names the
value to use; a project-filed copy must use a full Databricks connection id in
type.name form. A translation must agree with the project’s catalog and schemas.
If the work is not part of a migration, pass standalone=true to either tool. It
defaults to false; project_id defaults to null. A copy with row_limit is a
sample and is not tracked. Mission, scheduled-flow, and scheduled-job work is also
untracked. Calls from outside Genesis are untracked unless they specify project_id.
Genesis records each filed copy on the project’s landed goal by itself; nothing
needs to be noted by hand. Once a minute it looks for copies that have finished and
are not recorded yet. A copy still running is not checked: its table can match before
it ends, so Genesis waits for the copy to report how it ended. Its automatic look-ups
stop 24 hours after the copy was filed, but an ending reported later still gets the
copy checked and recorded. A failed copy whose check cannot answer (for example, a
stopped SQL warehouse) is also tried again only within those 24 hours; after that it
is checked again only when someone cites it. A finished copy is tried until it is
recorded.
A foundry_publish_notebook publish is bound the same way, by the pipeline the
script was translated from: in an ordinary chat with no covering project it is refused
before the workspace is touched, and standalone=true or project_id work as they do
for a translation. Once a bound publish lands, Genesis registers that version of the
notebook on the project’s rebuilt_and_ran goal, and each
databricks_action(action="run_notebook") of its workspace_path on the same
connection is filed under the project and recorded there by itself once the run ends;
the run’s answer says so in project_tracking, and says why when a run is not tracked.
A run proves only the publish it was filed against, and only while that publish is
still the pipeline’s latest: a later publish of the pipeline (to any path or connection),
or anything else published at its path, even untracked, makes it not count and makes an
earlier pass stale. Within that, a run counts when it succeeded, every task ran the
published notebook, and it started after that publish. A run that can never count (it
ran another path, Databricks no longer has it, or its publish named no connection) is
listed as “Cannot be checked”; any other run is checked automatically for 24 hours. A failed run is
shown as “Last run failed” with its reason, and the goal stays unmet until a later run
succeeds. Publishing again makes the goal stale for that pipeline until a run of the
new notebook is recorded. Name databricks_connection_id when publishing: runs of a
notebook published without one are listed under the goal as “Cannot be checked”, with
the reason, and never count. A planned project’s rebuilt_and_ran expects every pipeline
the plan can translate; a pipeline it cannot translate, such as a job spec, is a gap
of the plan.
A finished copy’s table must still hold that copy, with the dataset’s stamp and
Foundry’s row count. The table’s identity is read before and after: a table written,
or dropped and re-created, during the check is checked again. Each checked copy gets one note, shown as “Recorded by the server
for” the project’s person, which says when the copy was checked. That verdict is the
table as it was at that time; a recorded copy is not checked again. A check that
cannot answer yet (for example, a stopped SQL warehouse) is tried again after 1, 5, 15
and 30 minutes, then hourly; the project shows the copy as waiting to check, with the
reason.
An older copy whose table now holds a newer copy of the same dataset, filed under the
same project, reads as replaced and does not count; it never turns Landed failed.
A table that no longer exists, or that holds a copy which failed, is recorded as
failed. A copy that failed before it landed leaves Landed as it was, and a failed
copy is never counted as finished.
project_note never records a filed copy. Citing a filed copy’s receipt alone on the
landed goal writes no note. For a copy not yet recorded, it asks that the copy be
recorded with its next check’s verdict, normally within a minute; for a recorded copy
it answers with the recorded verdict and the time it was checked. Registering the
dataset by hand, a waiver included, is refused. The job can be paused with
jobs.project_evidence_recorder.enabled; filed copies then wait and are recorded when
it resumes. See Foundry copy evidence recording settings
for its default, cost, and timing.
Ontology coverage in the plan
foundry_plan_migration returns an ontology block alongside the pipeline plan.
It lists each tenant ontology and its object types as work to land with
foundry_materialize_ontology; each object type includes its API name, display
name, primary key, and property count. The block also reports
object_types_planned, action_type_count per ontology,
link_types_planned: false, and action_types_planned: false. Link types are not
materialized, and action types are returned as stubs to re-author.
Ontology coverage distinguishes an empty estate from a read gap. A tenant that
does not serve the ontology API has route_served: false and does not make the
plan incomplete. A listed ontology with no object types is also not work to land;
the tenant’s seeded default ontology may be empty, so the ontology’s presence
alone does not establish that it models data. If the token cannot list ontologies,
list_error is set and incomplete_reasons includes ontology_unexamined with
ontology: null. If a listed ontology cannot be expanded, it appears in
unreadable and gets its own ontology_unexamined reason. In either case,
plan_complete is false because ontology coverage is unmeasured, not absent.
See Palantir Foundry connections
for the required token access.
The ontology read costs one HTTP round trip to list the tenant’s ontologies and
one more per ontology listed. A two-ontology tenant therefore adds three calls
to the planner’s existing metadata reads. Planning reads metadata only; it does
not materialize ontology rows or write to Foundry.
During verification, the Prover includes Foundry-declared keys from dataset schemas and
published Pipeline Builder output checks when they can be resolved against the dataset
schema. See Foundry catalog and declared keys
for the scope and unread cases.
The translated script preserves Foundry’s output column names and case. It avoids
duplicate column names; if the compiler cannot determine a node’s columns in
advance, the script checks them at runtime and stops at that node if duplicates
are present. Before reading a derived input, the script checks whether its target
table exists. If it is missing, the error names the Foundry pipeline that builds
it and directs you to run that pipeline successfully on Databricks first, or
land a copy of the dataset from Foundry. If the producer is a job spec rather
than a Pipeline Builder pipeline, the dataset must be copied from Foundry.
Keeping Foundry copies separate from rebuilt tables
By default,foundry_translate_pipeline reads and writes in target_schema. To keep
Foundry input copies in a different schema, pass source_schema with the schema name
alone (not catalog.schema); a value containing . or no letters or digits is refused.
The generated script reads inputs that no Pipeline Builder pipeline builds from
source_schema; it reads this pipeline’s outputs and inputs built by another Pipeline
Builder pipeline from target_schema, where their producer writes them. Outputs are
always written to target_schema. Do not move or copy tables between the schemas; land
or rebuild each input in the schema the script reads. Use the same two schemas for every
pipeline in the migration so a downstream script looks for its upstream table in the
right schema.
next_step for each input instead of landing every input in one
schema. It gives the exact foundry_extract_dataset call: ordinary source inputs and
job-spec outputs go to source_schema; an input built by another Pipeline Builder
pipeline goes to target_schema if that producer is not being migrated; and an input
this pipeline also outputs is copied to target_schema only when its table does not
exist yet. If migrating an upstream Pipeline Builder pipeline, translate and run it
first with the same schemas. A dataset whose origin is unknown is assumed to be a
source input; check inputs_with_assumed_schema in the result and confirm that no
pipeline builds it before trusting a copy in source_schema.
Polling asynchronous extractions after restart
Usefoundry_get_extraction to poll an asynchronous extraction and
foundry_list_landings to inspect target tables. Every running extraction and
the 50 most recently finished remain in process memory. When a recovery watch
was recorded, its status can also be recovered after a backend restart, and a
terminal poll receipt remains available for seven days after its notice is
acknowledged. When the watch has a thread target, the background watcher can
notify that thread with the recovered status.
After a restart, interrupted means the copy was interrupted; may not have started means the extraction was recorded before its start was confirmed. In
either case, the landing may contain a partial copy and the target may already
have been promoted, so inspect both before retrying. no longer tracked means
the outcome is unknown. unavailable means a current-boot watch exists but its
copy is absent from this process’s memory. A cancelled result also carries an
error and no next_step; inspect the landing and target before retrying. If no
recovery record is available, the poll cannot establish what committed.
Each poll costs an agent tool call and may read saved status. Poll at a human
pace using the handle’s time estimate. A translated or executed pipeline is
not proof of equivalence; use foundry_verify_migration for the auditable
result.
Paging datasets without a unique key
foundry_extract_dataset reads datasets larger than one 50,000-row chunk in
pages. It first seeks along a column measured unique, or partitions by a column
whose values form groups that each fit in a chunk. If neither works, it hashes
every column to assign rows to buckets and reads each bucket once. The result
reports pagination_mode: "row_hash", with pagination_key_column describing
the split and partition_group_count and partition_max_group_size reporting
the bucket count and largest bucket.
In seek mode, Genesis writes each page of up to 50,000 rows to the Databricks
landing table before reading the next page. For asynchronous extractions,
groups_total is the planned number of seek pages or partition groups, and
groups_done counts pages or groups already landed.
Choosing the bucket count costs up to six Spark GROUP BY jobs over the whole
dataset; extraction then runs one filtered scan per bucket. Each bucket must
contain at most 50,000 rows, and the fallback stops at 2,000 buckets. If it
still cannot page the dataset, the error distinguishes a size limit (more than
100,000,000 rows) from more than 50,000 identical rows. Pass row_limit to
land a bounded sample; for the size limit, split the source into several
Foundry datasets, and for duplicate rows, aggregate them with a count column.
Receipts for finished dataset copies
A successful whole-dataset copy returnscopy_receipt_id, the id of a durable
receipt Genesis writes after promotion. The receipt records the dataset RID, target
table, Foundry and Databricks connection identifiers when available, the matching
source and landed row counts, and the copy id stamped on the table as the
genesis.foundry_copy_id table property. It also records the dataset’s branch head
before counting and after the copy only when that head’s status is confirmed as
COMMITTED. A missing head, a head not confirmed as committed, or an unreadable
branch/status leaves that point unknown; neither recorded head pins which rows were
copied. If the two heads differ, the result note says a transaction committed during
the copy. The receipt is evidence that Genesis completed the copy and the counts
agreed; it does not prove the table contents match Foundry. Use
foundry_verify_migration for that comparison.
Genesis writes a receipt only when the call did not include row_limit and both
counts are whole numbers that agree. For a dataset with no schema, the counts are
the number of files, with one landed row per file. A successful copy without a
receipt returns copy_receipt_id: null and copy_receipt_note explaining why; a
failed copy returns neither field. A non-null receipt can also have a
copy_receipt_note when a branch-head read or the Databricks connection
identifier needs attention. If databricks_connection_id is not a full
type.name identifier, the receipt does not record a Databricks connection. A
credential rejected on the branch-head read after promotion does not undo the
copy or receipt; the result carries fault: "credential_rejected" and
remediation, and the receipt’s ending branch head is unknown.
For a copy without row_limit, Genesis reads the branch and its head’s status
before counting. If the counts qualify for a receipt, it reads them again after
the copy. A completed head check uses two Foundry requests, each issued once
without retry or waiting on Retry-After. The five-second caller budget narrows
the branch request’s socket timeout, then passes whatever remains to the status
request instead of leaving it at the client’s 120-second default. If the branch
read consumes the whole budget, the status request is skipped and the head is
recorded as unknown. These socket timeouts bound connection and each wait for
bytes separately, not the total wall-clock duration. Writing the receipt adds
one write to Genesis’s metadata database. For async_mode copies, the receipt is
written when the background copy finishes and appears in
the successful foundry_get_extraction result, not when the extraction is handed
off. Copies from a backing dataset during ontology materialization follow the
same receipt rules; ontology objects paged through the object API are not dataset
copies and have no receipt.
Complex columns and conversion losses
foundry_extract_dataset lands Foundry ARRAY columns as Databricks arrays and
STRUCT columns as structs, including nested arrays and structs when their
declared shape can be carried. MAP columns, containers with missing or
unsupported nested types, and structs without named fields land as JSON text
instead. The result’s widened_to_string lists each such landed column as
<column> (<Foundry type>); use it to identify columns whose Databricks type is
text rather than the Foundry type.
The result’s values_lost_to_type reports per-column counts of source values
that could not be converted and were landed as NULL. A whole-copy receipt is
not written when this field is non-empty: fix the conversion loss before
treating the landed table as a complete copy. When the copy was filed under a
migration project, Genesis records it as failed, so it cannot count toward
Landed; the promoted table remains available for inspection. Nested member
conversion failures can null the whole array or struct value.
Pinning and protecting the extraction target
Passexpected_table as the full catalog.schema.table name when the copy must
land at one specific destination. Genesis refuses the extraction before reading
rows if the Foundry dataset name resolves to a different table. This also catches
copy_derived=true when its copy destination would be moved aside from the
expected table.
Set require_absent=true when the target must be created only if it does not
already exist. If the target exists when promotion runs, Genesis does not replace
it; the staged rows remain in the __landing_ table reported by
foundry_list_landings.
A rejected credential is refused, not planned around
These tools read a tenant one resource at a time, and each read degrades on its own so that a single unreadable dataset cannot stop a migration: its lineage is reported asunknown, its table takes a name derived from its rid, its schema
is left out of the map. That is the right answer for a metadata route a tenant
does not serve, and the wrong one for a rejected token, which fails every read —
the plan would then describe a sparse estate instead of naming the credential.
So a Foundry 401 refuses the whole call. The result carries success: false,
fault: "credential_rejected", the error Foundry returned, and a remediation
naming the instant this connection’s stored copy of the credential was
written. That instant is the one that matters: Genesis authenticates with the
copy in the connection’s secret row, not with whatever the tenant issued most
recently, so a token renewed in Foundry after that instant has never reached the
connection. A refusal that arrives after work has already been done — an ontology
type landed, seek pages or partition groups staged — still reports what was
written and the landing table it went to, because a refusal must not be the reason
a completed write becomes unreachable.
This covers 401 only. A token that is valid but lacks permission answers
403, and a 403 really is a fact about the resource it names, so those stay
per-dataset: the lineage reads unknown, the dataset is listed in
unknown_origin_datasets, and — because plan_complete is computed from external
ingestion, unreadable connections and dependency cycles alone — a scope whose
every dataset was denied can still report plan_complete: true with an empty
plan. Read unknown_origin_datasets alongside the verdict until that is
addressed.
Publishing and running are separate steps. foundry_publish_notebook writes the
notebook the customer’s engineers open and review; it runs nothing and reads no
data. When the publish result has execution_ready: true, run its
workspace_path with databricks_action(action="run_notebook"), pass an explicit
operator-approved cluster_id (an existing cluster id, or the literal
serverless only when approved), retain the returned databricks_run_id, and
poll it with databricks_action(action="check_run_status"). Do not omit
cluster_id: omission selects serverless implicitly. After a successful Job run,
call foundry_verify_migration once with rescan: true for each output mapping,
then poll or resume that same proof without starting another rescan. The publish
result also includes SHA-256 receipts for the generated script and imported
notebook; those establish byte chronology, not which bytes Databricks executed.
foundry_run_pipeline_script remains a diagnostic Spark Connect path. It can run
the generated script and report what it wrote, but it creates no Jobs run receipt
and is not the intentional-demo execution record. Its declared_output_tables
lists literal table names in .saveAsTable(...) calls, including calls with the
table name on the line after the opening parenthesis. On success,
output_tables reports those tables with their row counts.
A run still going after about four minutes has not failed. The call returns
status: "running" with a pipeline_run_id, and the run continues. Poll
foundry_get_pipeline_run for its result instead of calling
foundry_run_pipeline_script again, which would start a second run over the same
tables. Pipeline runs are tracked in the backend’s memory, and only the 50 most
recently finished runs stay pollable. After a backend restart, or once 50 later
runs have finished, the id is unknown; the output tables show what the run wrote.
At most four pipeline runs go at once in one backend. A fifth call is refused with
status: "not_started" and the ids of the running ones to poll; it starts nothing.
Each output whose Delta version changes during the run is marked as a rebuild, even if the
script later fails. The run attempts to remove any Foundry-copy or sample stamp and sets
the table property genesis.foundry_rebuilt_by to foundry_run_pipeline_script, so a
marked output cannot be adopted or read as a landed copy. If the script succeeds but a
changed output cannot be marked, the tool returns success: false and names the unmarked
tables. Cost: one full Spark run that reads the inputs and overwrites the outputs, one
count per output, two Delta history reads per declared output (before and after), and two
property statements per output the run wrote.
What publishing will not overwrite
The workspace import replaces whatever is at a path and offers no precondition, sofoundry_publish_notebook establishes what is there first, with one export,
and refuses rather than replacing. It publishes onto a path Databricks confirms
does not exist, and over a notebook whose provenance header records the same
Foundry pipeline — that second case is how a re-translated pipeline updates its
own notebook. It refuses a notebook belonging to a different pipeline, a notebook
carrying no provenance to identify it by, a script that carries none of its own,
and a path the export could not read at all; each refusal names the reason and
points at name for publishing under a distinct notebook name. Two Foundry
display names can fold to one workspace name (a/b and a.b both become a_b),
which is what makes the check necessary rather than defensive.
A script is also refused when it carries the Databricks cell separator
(# COMMAND ----------) on a line of its own: Databricks reads that line as a
cell boundary wherever it appears, so the notebook would be a different program
from the script that was translated and run.
Tool reference
Verifying a translated pipeline output
Whentarget_table was built by a translated pipeline, pass the script_path
returned by foundry_translate_pipeline as translation_script_path. The result
includes non_reproducible_by_construction, mapping each recorded column to its
derivation: these values are drawn afresh on every run, on Foundry and Databricks,
so they cannot match by construction. In a Prover proof, value, content, and count
readings for these columns are indeterminate with cause
non_reproducible_by_construction—neither a difference nor a match—unless a person
licenses them; structural findings are unaffected. The full-table digest excludes
these columns and compares the rest. The quick_keyed_check mode does not use this
Prover classification and still counts mismatches in them. See the
Prover comparison guide
for how to interpret the cause. The translated script is read once from the uploads
repository for this check.
If the script predates the NON_REPRODUCIBLE_COLUMNS record or was edited to
remove it, non_reproducible_by_construction is null and
non_reproducible_note says no columns were checked. An empty mapping means the
translation recorded that no output columns are non-reproducible. Omit
translation_script_path for a landed source dataset.
The Prover comparison performed by foundry_verify_migration also explains when a
float tolerance could not be checked and can license a BigQuery TIME-to-text landing only
when live values prove the canonical representation; see the
Prover comparison guide.
Comparing a landed Foundry copy
Whenfoundry_extract_dataset(copy_derived=true) lands a dataset, it checks whether the
destination table is absent or stamped as an earlier copy of that same Foundry dataset.
In either case, the copy lands under the dataset’s table name. Otherwise the existing
table is left untouched and the copy lands as <table>__foundry_copy; the result’s
table names the copy and left_untouched names the spared table. If that alternate name
is also occupied by something other than this dataset’s copy, or the warehouse cannot
confirm the tables’ status, extraction is refused. This applies regardless of the
dataset’s reported origin and lets a translated pipeline’s rebuilt table stay intact for
comparison.
The destination checks for copy_derived=true cost one to four Databricks statements
before rows are counted or extracted.
Do not repeat a copy_derived=true landing after a pipeline has written that table: a
rebuilt table can retain the earlier copy’s stamp and then be replaced. A landing without
copy_derived does not check the table’s stamp and replaces the table with the dataset’s
name, so do not use it where a pipeline has rebuilt that table.
Pass the landed table to foundry_verify_migration to compare it with the rebuild:
foundry_copy_table is optional (default ""), applies only to
quick_keyed_check, and requires key_columns. The copy and target must be
three-part Databricks table names. The tool describes both tables, then compares
them in one Databricks statement; the diff scans and joins the two tables in the
warehouse and returns totals plus at most 50 sample keys. It does not read Foundry
or create a Prover run, so it compares the copy as it was landed, not the current
tenant data. Float, double, and real columns use the relative tolerance
float_rel_tolerance (default 1e-6). Duplicate keys make the verdict
INDETERMINATE; columns present on only one table make an otherwise matching
result FAIL.
Without foundry_copy_table, quick_keyed_check pages Foundry and Databricks by
key. That path has a 200-second budget; if it runs out, the tool returns an
INDETERMINATE verdict with keys localized so far, truncation_reason: "time_budget", and a next_step to land Foundry’s rows and compare the copy.
Use mode: "prove" instead when you need an auditable Prover comparison.
If background hand-off is refused for an asynchronous foundry_extract_dataset
or a large foundry_materialize_ontology, the tool returns immediately with
success: false, status: "failed", an extraction_id, and an error. The
extraction was not started; the returned ID identifies the failed attempt, not
queued work.
Verification wait budget
foundry_verify_migration always returns its answer inside the bot turn. Its
inline wait has one 240-second budget for the entire call, not a separate allowance
for each run. The space walk, estate reachability checks, and target description
also use time from this budget. The wait_seconds parameter (default 0) requests
how long to wait inline; values above the time remaining in the budget are clamped.
Negative values are rejected. Leave it at 0 to submit work without waiting inline.
The call can submit two characterizations and a proof. If the inline wait ends
before those runs finish, they continue running and the answer includes their run
ids so you can poll them with get_run; the wait ending does not stop the work.
Long-running tools return handles. For verification, stage identifies whether the
run is still characterizing, proving, or grading; poll the returned run ids
with the Prover’s get_run tool or call foundry_verify_migration again with the
same arguments. When spec_hash is supplied, Genesis reads and verifies the sealed
specification before estate I/O and attaches it to the proof. grading means the
comparison exists but its specification grade is still running; wait for the proof
to reach a terminal status before treating it as a verdict. spec_hash applies only
to mode: "prove"; supplying it to the keyed-contract quick check is refused.
A characterization can enter a proof only after its own run succeeds and exposes
a sealed snapshot. A terminal non-success is reported and does not fall back to an
older snapshot or start a replacement characterization.
Foundry reads that return exactly 1,000 rows may be refused if the preview role could
have capped them; see Foundry SQL result caps
for the probe, access remedy, and limits of this check.
foundry_verify_migration returns stage: "withheld" when its comparison compared
no readings and at least one reading was withheld. Treat this as not proven, not
as a clean result: the counts are empty because nothing was measured. Read
comparison.value_coverage.statement for what was not compared. When
comparison.value_coverage.recharacterize identifies stale side(s), call
foundry_verify_migration once with the same arguments and rescan: true to start
fresh measurements; if it returns stage: "characterizing", poll with get_run or
call again without rescan: true, because repeating the rescan restarts the scans.
When recharacterize is null, no re-scan is prescribed because the comparison
could not identify which side to measure again.
Every foundry_verify_migration answer starts with audience: "agent" and
audience_note, marking its proof result or refusal as guidance for the calling agent,
not text to forward. See Prover guidance
for how to report the result to a person.
For the comparison card shown when verification runs in chat, including its live Prover
theater and migration pass score, see Review agent-generated comparisons in chat.
Adopting an existing landed copy
Dataset copies landed byfoundry_extract_dataset without a row_limit are stamped
with the Foundry dataset RID they copy. A row-limited derived-dataset copy is also
stamped, but marked as a sample and not used as a dataset source in a spec grade; a
row-limited source-dataset landing is unstamped unless it uses copy_derived=true.
These stamps let an eligible keyed
Foundry-source obligation read the already-landed rows from Databricks; the Prover guide
explains when that route is selected and how its evidence is reported.
Use foundry_adopt_copy only when the table owner confirms that an existing table is
the copy of the named dataset. It does not land or rewrite rows. It adopts an unstamped
table only when Delta history attributes its last write to manual_query; a write
attributed to a job, notebook, or pipeline, an unreadable history, a stamp for another
dataset, or the rebuild marker genesis.foundry_rebuilt_by is refused. If the table
already carries the requested stamp, the tool returns adopted: false without writing.
table, dataset_rid, and adopted, or success: false and an
error. Adoption can issue up to three statements on the Databricks warehouse: read
table properties, inspect Delta write history, and write the copy stamp.
For how the Prover verifies a Foundry copy that is already in its target, see the data-work proving skill.
Materializing large ontology types
foundry_materialize_ontology measures each selected object type before reading
its rows. Types with at most 50,000 objects use the ontology object API. A type
above that limit is not paged through the API; pass space_rid for the Foundry
space or folder whose pipelines build the ontology so Genesis can resolve the
pipeline-output dataset and extract it through the same staged, chunked path as
foundry_extract_dataset.
The backing-dataset match requires the object’s exact primary-key column and at
least 90% of its properties as dataset columns. If no dataset matches, more
than one matches, the count cannot be measured, or space_rid is omitted, the
object type is listed in failed_object_types rather than guessed or paged. A
successful backing-dataset entry reports materialized_from: "backing_dataset",
the backing_dataset_rid, and provenance in the form
materialized_from_backing_dataset <rid>. These are the dataset’s rows; an
ontology that filters, joins, or derives properties may not match them row for
row. Large successful materializations can be asynchronous; poll their
extraction_id with foundry_get_extraction.
Costs and safeguards
foundry_extract_dataset, foundry_materialize_ontology when it uses a backing
dataset, foundry_run_pipeline_script, and foundry_verify_migration run
customer compute. When foundry_verify_migration receives spec_hash, grading
also reads both estates. Configure Foundry request timeouts in Advanced
Settings.
Foundry resource identifiers are stable migration keys. Keep the source dataset RID,
the target table, proof_run_id, and diff_hash together in the migration report.
