DataShield Ontology · documentation

Datasets, Analysis and Transforms

Measured against: /home/datashield/library at v0.22.188 (package.json:3), 2026-09-22. Audience: developers, analysts, AI agents routing through the MCP surface. Method: every claim below is read from code or migrations and cited file:line. Where the platform does not do something, this document says so explicitly rather than implying capability.


On this page

1. The dataset model — registry.datasets

The dataset is the unit everything else attaches to. Its row was created in migrations/registry/0001_init.sql:102-142 and extended by later migrations. The base columns:

GroupColumns (from 0001_init.sql:102-142)
Identityid UUID PK, provider registry.provider_type NOT NULL, provider_dataset_id
Descriptiontitle, description, publisher_name, jurisdiction_level, jurisdiction_name, license, tags TEXT[], topics TEXT[]
Sourcelanding_url NOT NULL, landing_url_canonical NOT NULL, raw_page_payload JSONB, raw_source_payload JSONB, steward_notes
Livenessis_active, confidence, discovered_at, last_checked_at, last_changed_at, next_check_at, check_status, http_status, content_hash
Usageread_count, search_hit_count, last_accessed_at, search_tsv
Auditcreated_at, updated_at

Later migrations add 53 more columns by ALTER TABLE registry.datasets ADD COLUMN (measured across migrations/registry/*.sql; none is ever dropped):

FamilyColumns
Profile / shapecolumn_count, row_count, file_count, total_file_size_bytes, source_file_size_bytes, completeness_score, quality_score, quality_rating, has_analysis, table_type, dataset_type, file_type
Governancecompliance_level, has_pii, has_phi, classified_by, last_classified_at, needs_classification, visibility
Provenance / capturesource_category, source_system, source_mode, source_total_rows, row_count_drift, provider_metadata, capture_metadata, extras_json_text, notes_json_text, ai_tags, ai_tags_updated_at
Lifecyclelifecycle_stage, deleted_at, deleted_by, purge_after, content_erased_at
Query enginequery_engine, query_engine_loaded_at, query_engine_last_refresh_at, query_store_size_bytes
Refreshlast_refresh_status, last_refresh_actor, last_refresh_attempted_at, last_refresh_completed_at, last_refresh_duration_ms, last_refresh_message, schedule_next_run_at
Snapshot / archivesnapshot_count, snapshot_total_size_bytes, archive_total_size_bytes
RAGrag_indexed_at, rag_index_generation, rag_index_size_bytes, rag_indexed_record_count

The derivative writers use a 16-column subset of that surface — visibility, source_mode, provider_metadata, capture_metadata and the governance trio among them (scripts/mcp-server/umbrellas/dataset_admin.ts:1258-1262).

Two facts an agent must not assume:

(conduit.repo_files.parent_file_id / derivation_type / derivation_config / pipeline_hash), not on the dataset (docs/audit/portal-ontology/05-library-derivative-datasets.md:114-116).

(latest load wins) then registry.dataset_settings; neither → typed workspace_unresolved (lib/analytics/deepAnalysis.ts:65-79).

Field-level metadata is a sibling table, registry.dataset_fields, populated by the analysis bridge (scripts/worker.ts:1030-1040, importAnalyticsProfile) and read by dataset_catalog (dataset_catalog.ts:63).


2. Ingest paths

Four doors write datasets and files. All URL fetches are SSRF-guarded (dataset_ingest.ts:821).

DoorCommands (measured case list)What it accepts / doesSource
dataset_ingestingest, cancel, url_ingest, batch_ingest, url_and_wait, upload_urls_get, load, unload, status_get (9)Inline content ≤10 MB (CSV/JSON, or PDF/DOCX/TXT/EML/HTML → document_collection dataset); URL download; async tabular batch (HEAD preflight sync, returns batch_id); url_and_wait tabular refresh orchestrator (job_id within 2 s); load into Query DB (masking_mode='physical' is async); unload is ADMIN + confirm:true. HTML pages are preflight-rejected by URL (LIB-BUG-210) — ingest inline.dataset_ingest.ts:819-840, 950-1021
file_repolist, read_file, analysis_runs, derivatives_list, summary, versions_list, derivative_create, drift_events, drift_acknowledge, download_url, upload (11)Conduit file-repository access. Reads public; writes require API key. upload adds a file version; derivative_create stores caller-supplied bytes as a child of parent_file_id (§6).file_repo.ts:189-198, 322-480
crawlerproject_* (6), source_* (5), portal_discover, dataset_refresh (13)Open-data portal crawls (socrata/arcgis/ckan/data_gov providers, :150), portal discovery over data.gov / global Socrata / known-portals registry (:161), and dataset_refresh → async refresh_analyze job (:139). scope.sample_policy:'plain' requires acknowledge_plain_samples:true, recorded as an audit row (:150).crawler.ts:123-162
restendpoint_create, endpoint_update, endpoint_delete, endpoint_list, execute, responses_diff (6)REST endpoint CRUD + execution; auto_save attaches the capture file_repo to the dataset (:200); optional cataloguing enqueues a discover scan on the account's rest_api catalog provider — refused endpoint_unbound_not_catalogable without auth_connection_id (:89).rest.ts:24-89, 207-540

Post-analysis loading is a single seam: finalizeIngestAfterAnalysis (lib/ingest/postAnalysisLoad, called at scripts/worker.ts:912-913 and :940-941) — auto-load and downstream steps run there, not inside each engine.


3. Analysis engines

Jobs are dispatched by job_type in scripts/worker.ts. The engine name stamped on the stored run is what dataset_analysis.runs_list and file_repo.analysis_runs later report.

job_type (dispatch line)Engine name stampedWhat runs
refresh_analyze (worker.ts:317)inherits the analysis pathrefreshDataset (lib/conduit/crawlerDownload) re-downloads by URL, then re-analyses and re-runs derivatives.
dedup (worker.ts:422)pg (:537)FEAT-013 async entity dedup in the Query DB against a composite dedup_key (FEAT-009 key groups, :534); writes a _dedup_<ts> derivative table and pre/post/removed counts.
deep_analysis (worker.ts:615)— (writes registry.rel_atlas)runDeepAnalysis → RelationshipAtlas (§3.1). completeJob with the consumed conduit runId (:625).
rag_embedding (worker.ts:627)— (no engine)Declared but not implemented. The branch throws rag_embedding job type is not yet implemented (:628; same in worker-child.ts:193-196), and the type is listed in NOT_YET_IMPLEMENTED_JOB_TYPES (lib/conduit/analysisJobs.ts:46), which is what removes it from the dataset_analysis.submit wire enum. RAG indexing runs on its own lane, not through this worker.
full_analysis / reprocess (worker.ts:629)duckdb_profiler (:728)Large CSV, any-size Parquet and routed JSON go to DuckDB: computeRawProfile + formatProfile (lib/conduit/duckdbProfiler, :715-719), stored via storeAnalysisRun, then bridged to registry.dataset_fields/dataset_analytics_profiles by importAnalyticsProfile with engineName:"duckdb_profiler", engineVersion:"1.0" (:749). LIB-BUG-269: small Parquet used to be dark because the in-memory engine cannot read PAR1.
same, document branchdocument_processor (:914) / document_disposition (:929)analyzeFileBuffer (lib/conduit/analyzeFile) text-extracts documents; a document with no extractable text stores a document_disposition run instead of failing (:918-944).
same, in-memory branchworker_processor (:1016, :1036)Default in-memory tabular analysis; bridged with profileType:"conduit_analysis" (:1038).

Execution mode per account (client / server / auto) is read and set by dataset_analysis.analysis_mode_get/set and honoured by the server-side paths only (dataset_analysis.ts:1008, :1016).

3.1 RelationshipAtlas — deepAnalysis

lib/analytics/deepAnalysis.ts:1-24 states the contract, enforced in the module:

atomically — the only write is a single INSERT into registry.rel_atlas after the full build succeeds (:9-10).

analytics config store with hash-verified pins (thresholds, relationship-policy; :98-104).

via corr() through the masked-view query engine, bounded by compute_row_limit; r² = r·r (:17-22).

3.2 The AnalysisContract sections

config/analysis-contract-sections.config.ts:10-15 is the AnalysisContract v1.1 vocabulary (20 sections). Both engines stamp the full list on metadata.sectionsIncluded; a partial producer (the catalog pushdown profile) stamps a declared subset so a renderer shows which sections are absent instead of reading an empty section as "clean" (:2-8). The config carries names only (17 lines; no per-section prose) — the descriptions below are the author's reading of the producers in §3.

SectionCarries
executive-summaryHeadline row/column counts, quality verdict, top findings
data-completenessPer-column null/missing percentages
field-statistics, column-statistics-summaryPer-column type, cardinality, min/max/quantiles (value-bearing members are withheld on public reads — §4.3)
top-valuesMost frequent values per column (value-bearing; masked/withheld on egress)
patterns-detectedClassification hits from the pattern library (semantic types, PII/PHI)
before-after, data-pipeline, original-sample, final-sampleTransformation-pipeline effect and samples when a pipeline ran (JSON ingest path)
dataset-profile, column-semanticsDataset-level shape; per-column semantic type used by dataset_catalog.fields_search
relationship-graphColumn relationships (deepAnalysis populates registry.rel_atlas separately)
data-quality-metricsQuality score inputs (registry.datasets.quality_score/quality_rating)
compliance-governancecompliance_level, has_pii, has_phi, framework hits
transformation-lineage, migration-playbook, business-rulesLineage narrative, import-SQL guidance, inferred rules
metadataEngine name/version, sectionsIncluded, vocabulary_version (used by submit_batch scope:'vocabulary_stale', dataset_analysis.ts:1006)
source-fingerprintSource-system detection result (§9, fingerprint)

4. Reading analysis and data

4.1 dataset_analysis (8 commands, dataset_analysis.ts:998-1017)

get (by run_id > file_id > dataset_id precedence, :1028-1034), runs_list (offset-paginated run history), create (store an analysis result with engine_name/engine_version), submit (enqueue full_analysis / refresh_analyze / reprocess / dedup / deep_analysis — the wire enum is SUBMITTABLE_JOB_TYPES, derived as JOB_TYPES minus NOT_YET_IMPLEMENTED_JOB_TYPES, :63-69), submit_batch (ADMIN staged re-analysis; dry_run defaults true; scopes stale / all / vocabulary_stale; DB-enforced idempotency against active jobs), preflight (HEAD+1 MB URL probe), analysis_mode_get/set. Job lifecycle moved to the job umbrella (:1017). Annotated destructiveHint:true because submit(refresh_analyze) replaces the stored file (:1018-1024).

Two state-source disagreements found while measuring this umbrella (META-CALIBRATION 5 — recorded, not resolved here):

describes the full body as "19-section" (:1035) — while the AnalysisContract vocabulary both engines stamp carries 20 sections (§3.2). The prose predates column-statistics-summary / source-fingerprint; the config is the authority.

:63-69). §10's deep_analysis row routes through submit and is correct.

4.2 dataset_data — the read-only query door (2 commands, dataset_data.ts:307-336)

2+ datasets (dataset_ids + aliases, cross-schema). Engine routing is automatic — PostgreSQL for loaded datasets, DuckDB directly on the file for unloaded/large ones (:311). The read-only guard rejects forbidden keywords, second statements and file/network table functions (:204). Positional $1 bind params work on every engine (:327).

totalRows, routing.engine (:313).

4.3 dataset_catalog get / get_batch and the field-row projection

10 commands (dataset_catalog.ts:113): search, get, get_batch, stats, tables, fields_search, similar_list, change_history, catalog_facets, catalog_stats. get returns fields/resources/lineage/quality plus a row_drift signal (observed row_count vs provider source_total_rows, :118); get_batch does the same for 1–50 ids (:123). Field rows come from registry.dataset_fields (:63).

Field rows are value-free on this surface. sample_values and value-bearing statistics (min/max/quantiles) are withheld under the platform sample-value policy (default omit) because the catalog read is public-audience; each field instead carries sample_values_withheld {reason, policy}; count statistics (missing %, uniqueCount, cardinality) are returned in full (LIB-BUG-1395; :185, :220). The acknowledgement path that would release samples is unreachable on this door — no door passes it (:595). compact:true trims each field to a short projection (:263-264, :578-579); include_fields:false drops the list and keeps fieldCount (:261-262, :575-577). Column counts are reconciled against the field list (LIB-BUG-416, :258).


5. Derivatives that create a NEW dataset — dataset_admin.join_create / union_create

dataset_admin has 13 commands (title dataset_admin.ts:2444; schema union :2462-2602; handler cases :2625-2705) — settings_get, settings_update, bulk_action, summary_refresh, masking_reload, masking_set, changes_list, join_create, union_create, catalog_export, sync, refresh, columns_align. The two derivation verbs are join_create (:2644) and union_create (:2645).

Propertyjoin_createunion_create
Enginein-memory DuckDB (duckdb-async, Database.create(":memory:"), :796, :845-846)same
Readseach source through its served masked relation — masked columns are materialised as masked tokens, never parent plaintext (:2455); a masked join key that is type-incompatible across sides refuses with a remedy (:1419-1430)same masked-read contract (:2456, :1573)
Writesnew registry.datasets row (:1257-1264) + new conduit.repo_files row with derivation_type='join' (:1300-1305)new dataset (:2110-2131) + repo_files row (:2147-2152)
Governanceborn with strictest-of-parents compliance_level/has_pii/has_phi + framework union (:2455)same (:1554)
Cardinality guardestimates output rows; refuses join_cardinality_exceeded unless confirm_large_output:true (:2517)
Analysis re-enqueuedenqueueAnalysisJob full_analysis unless auto_analyze:false (:1356-1360)full_analysis priority 5 + optional async dedup job (:2186-2199)
Parent linkJSONB only: derivation_config.join_sources[{dataset_id,file_id,role}] (:1308-1312); parent_file_id is NULLderivation_config.union_sources[...] (:2156); parent_file_id NULL

Consequence measured in docs/audit/portal-ontology/05-library-derivative-datasets.md:125-126: the 17 live new-dataset derivatives (6 join, 11 union) have parent_file_id NULL; registry.asset_edges holds 0 dataset→dataset rows and derived_from is declared with no writer (:118-119) — so the lineage canvas renders a join/union output with no upstream (:128-132). The OpenLineage store (M-1/M-2) is the planned fix, not a new materialiser (:133-135).


6. Derivatives inside an existing repo — file_repo.derivative_create

file_repo.derivative_create (file_repo.ts:403-442) stores caller-supplied base64 bytes as a new conduit.repo_files version under parent_file_id with derivation_type/derivation_config, optional analysis attach (:198, :251). The platform computes nothing here — it records what the caller computed (05-...md:148). It is dual-target gated (repo mutate + parent-file read, LIB-BUG-407; :316-317); a concurrent version allocation returns a retryable conflict (:509). No new dataset is created.


7. The declarative transform model — dataset_transform

7.1 Vocabulary (config/transform-model.config.ts)

TRANSFORM_TYPES (:55-77) — exactly 21, asserted at :797: `remove_column, rename_column, add_column, reorder_columns, filter, mask_pii, standardize_date, type_cast, deduplicate, aggregate, join, validate, custom_sql, custom_python, fill_null, trim, uppercase, lowercase, regex_replace, split_column, merge_columns`.

ConfigValue (measured)
TRANSFORM_STATUSES (:92)supported, narrowed, extension, unsupported
TRANSFORM_STATUS_CENSUS (:701-705)supported 9 · narrowed 3 · extension 5 · unsupported 4
TRANSFORM_KINDS (:102)column, header, row_set, derived
CODEGEN_CENSUS.pandas (:714-715)emit 17 · recipe 2 (aggregate, join) · refuse 2 (custom_sql, custom_python)
VALIDATION_RULE_ACTIONS / _SEVERITIES (:693-694)log | filter | fail — and warning | error | fatal
EVALUATION_FAILURE_POLICY.mode (:751-752)count_and_report (alt. fail_closed)
GuardsREGEX_GUARD (:254), COLUMN_IDENTIFIER_RE + reserved list (:233-241), COLUMN_NAME_WIRE_CLAMP=128 (:231), PREVIEW_CONTRACT (:290)

Each type has a TRANSFORM_MODEL[type] spec (:358-690) and the module self-checks duplicates, model coverage and the census on load (:793-810).

7.2 Commands (dataset_transform.ts:113-244)

json_to_csv, json_process, transform_preview, transform_recipe_list, transform_recipe_get, transform_codegen.

row/column counts, per-column before/after shape tokens, change summary, flatten-spec and frame digests. Returns no cell values by construction and refuses if its own output scan finds one (:42). Unsupported step types (aggregate, join, custom_sql, custom_python) are refused with the reason, never dropped (:46).

pipeline, input-column assertion) plus attribution rows. Every caller literal is bound to a named parameter, never interpolated. Expression constructs and bare regex literals with no faithful re form refuse the whole request rather than emit code that computes something else (:43). attach writes the file as a derivative (text/x-python, derivation_type transform_codegen) into a repo the caller owns; parent_file_id must belong to repo_id (:81-82); ownership is checked before a byte is generated (:104).

7.3 P-3 — the no-execution invariant

"GENERATE-ONLY: the platform writes the file and NEVER runs it; there is no Python runtime, interpreter or sandbox on this surface" (dataset_transform.ts:43, :245). The invariant lives in config/codegen-no-execution.config.ts (05-...md:77). Note from the same audit (:80): no audit:codegen-no-execution gate exists in package.json — prior docs naming it as a gate were wrong; test:transform-engine and test:transform-bundle-smoke are the chained tests (package.json:98).

7.4 Honest statement — the model does not materialise a dataset

Per docs/audit/portal-ontology/05-library-derivative-datasets.md:26,58-59,137-146: there is **no transform_apply / materialize verb and no job kind runs applyTransformations**. transform_preview is a simulator; transform_codegen produces a real artefact that is never executed; the only path that applies the 21-type pipeline to rows is POST /api/conduit/json/ingest (REST, JSON input only), which writes a derivative file in the parent repo — not a dataset — and has 0 recorded uses (:143, :66). The only verbs that persist a new dataset from existing ones are join_create and union_create (§5).


8. Snapshots, lifecycle, publish / sync / archive / export

dataset_snapshot (7 commands, dataset_snapshot.ts:33-43): point-in-time snapshots of the loaded source table. create (server prefixes _snapshot_, bare label and handle both resolve), list (+ retention summary), get, compare (row-level diff, natural key auto-resolved from analysis else synthetic _row_id with a load-order caveat, :54), schema_diff (added/removed/renamed columns + type changes), delete and restore (zero-downtime swap, irreversible, confirm:true). Not a changeset reader: dataset_admin.changes_list (dataset_admin.ts:2640handleAdminChangesList, :474-494) runs queryCdcLog over the loaded schema's CDC log and refuses dataset_not_loaded otherwise — it does not read the changeset_computed derivative files auto-diff writes (05-...md:90, :147); nothing on the MCP surface reads those today.

dataset_lifecycle (6 commands, dataset_lifecycle.ts:360-370): delete (soft default; hard = DROP SCHEMA CASCADE, confirm:true, force on certified/reference, and mdm_disposition retain|purge REQUIRED — LIB-BUG-569, :379), undelete (refuses when content was erased, :533-541, or the dataset is hollow, :587-598), archive_create (S3/B2/R2/ Wasabi/Spaces/MinIO, optional drop_after), archive_list, archive_delete, restore. All commands take canonical dataset_id; bare id strict-rejects (:364). Stages: there is no dataset_admin.set_lifecycle_stage command — the stage is set either by dataset_admin.settings_update {lifecycle_stage} (dataset_admin.ts:2475) or by dataset_admin.bulk_action {action:'set_lifecycle_stage'} (BULK_ACTIONS, lib/lifecycle/bulkActions.ts:15-23), both over the same closed enum certified | reference | published | development | test | archived | uncategorized (config/lifecycle.config.ts:17-25).

Publish / sync / export (dataset_admin.ts:2457-2458):

(dataset / schema / lineage / quality / governance / import SQL / README / manifest) plus by default the data file; apply_masking / row_limit / columns apply to the uploaded file and governance.json's data_file claim states exactly what was applied; a destination that cannot honour requested encryption refuses archive_encryption_unavailable rather than upload cleartext (:611, :2458).


9. Generalization, fingerprint, evidence

generalization (7 commands, generalization.ts:136-147) — privacy-preserving column transforms. view mode is reversible masking applied at query / prompt-egress time and works; physical mode returns not_implemented (FEAT-033, :138, :141). Strategies and constrained options: dob_generalize (year|decade|age_range), zip_generalize (3|4 digits), age_band, phone_partial, ssn_partial, email_domain (:147). preview needs a loaded dataset; stats returns utility score + compliance coverage.

fingerprint (6 commands, fingerprint.ts:71-84) — identify the source system (Salesforce, SAP, Workday, …) from column names, sample values and metadata; 95 built-in + custom definitions (:77). detect scoring is IDF-weighted with calibrated confidence, raw score as the ranking key, matchedSignals and a basis string (:79); validate dry-runs ReDoS/length/count checks; create/delete are Developer tier+, creator-or-admin scoped. Feeds the source-fingerprint contract section (§3.2) and registry.datasets.source_system.

evidence (4 commands, evidence.ts:20-26) — the "prove" leg of find→prove→animate→watch. Rows are written at story.scan, pin their snapshot in the retention index and hash the payload over RFC 8785 canonical JSON. show (variant:'public' re-serves through the egress gate; PHI datasets refuse public without a §164.514(b)(1) determination), verify (re-hash vs content_hash), list (per claim), determine (admin; append-only PHI-publication determination; revocation is a new row, :26, :27-32).


10. Quick routing table for agents

NeedCall
Add datadataset_ingest.ingest / url_ingest / batch_ingest; documents inline via ingest
Make rows queryabledataset_ingest.loaddataset_data.query / sample (masked)
Metadata, fields (value-free)dataset_catalog.get / get_batch / fields_search
Run or read analysisdataset_analysis.submit (poll job.get) / get / runs_list
Relationshipsdataset_analysis.submit {job_type:'deep_analysis'}registry.rel_atlas
New dataset from two or moredataset_admin.join_create / union_create (DuckDB, masked-read, analysis re-enqueued)
Preview or generate a pipelinedataset_transform.transform_preview / transform_codegen — nothing executes (P-3), nothing materialises
Store a computed filefile_repo.derivative_create (caller-computed bytes)
Point-in-time / diffdataset_snapshot.create / compare / schema_diff
Publish a governed copydataset_admin.sync (sidecars + masked data file)
Cold storage / deletedataset_lifecycle.archive_create / delete (hard requires mdm_disposition)

Measured against the release named in the text. Raw Markdown: /ontology/documentation/datasets-analysis-transforms.md. This page is also served as raw Markdown at the same URL with a .md suffix, for agents and search tools that prefer plain text. Every capability statement cites the source file and line it was measured from.

You've seen the proof

Ready for a number? Scope your deployment and we'll price it against your own economics.

Get your quote →