ETLantic Profile for DTCS Portable Transformation Plans¶
Status: ETLantic integration profile draft over published DTCS 3.0 semantics
DTCS plan identifier: dtcs.transform-plan/2 (v1 remains readable)
ETLantic authoring profile: etlantic.transform/1
Target milestones: 0.11 kernel through 0.15 advanced lowering
1. Scope¶
This specification defines ETLantic's closed, versioned, backend-independent
serialization and authoring profile for a DTCS Transformation Plan. DTCS owns
transformation semantics, expressions, functions, semantic actions, and engine
capability meaning. This specification defines how ETLantic realizes that
model through its authoring profile and the requirements its plugins MUST
preserve. Canonical plan models and semantic registries belong to the public
dtcs package.
It does not replace DTCS or define physical execution, storage, scheduling, backend APIs, or arbitrary Python translation. Where this document and DTCS conflict, DTCS is authoritative and this profile must be corrected or versioned.
DTCS 3.0.0 and dtcs 0.14.0 are normative for Transformation Plan, Portable
Relational and Rich Portable Analytics profiles, registry, and conformance
semantics. DTCS 2.0.0 identifiers and dtcs.transform-plan/1 remain readable.
The key words MUST, MUST NOT, SHOULD, SHOULD NOT, and MAY in this document
apply only to ETLantic's authoring and compiler integration. They do not
redefine published DTCS meaning.
2. Architectural boundary¶
Transformation ports and contracts
+
Portable authoring expression
↓
TransformationIR
↓
Capability validation
↓
Backend compiler and execution plugin
The relationship is:
DTCS Transformation Contract
↓
DTCS Transformation Plan semantics
↓
DTCS `dtcs.transform-plan/2` representation
(v1 remains readable)
↓
Backend Execution Plan
The IR MUST be immutable, deterministic, bounded, data-only, inspectable, and free of executable objects and resolved secrets.
3. Document model¶
A serialized definition has this conceptual shape:
{
"protocol": "dtcs.transform-plan/2",
"transformation_id": "example.NormalizeCustomers",
"inputs": {
"customers": {
"contract_id": "example.RawCustomer"
}
},
"parameters": {
"minimum_age": {
"type": {"kind": "integer"}
}
},
"outputs": {
"result": {
"contract_id": "example.Customer",
"expression": {"kind": "project"}
}
},
"requirements": {
"profiles": ["dtcs:profile/portable-relational-kernel/1"],
"actions": ["dtcs:filter", "dtcs:project"],
"functions": ["dtcs:concat_ws"]
},
"fingerprint": "..."
}
Canonical serialization MUST sort mapping keys, preserve expression-list order, omit runtime values, and reject non-data objects. Fingerprints MUST be computed from canonical semantic content rather than display metadata. Compilers that claim only plan v1 MUST reject plans that require lambda nodes or other v2-only constructs.
4. Type system¶
The IR uses the DTCS logical type vocabulary (2.0 retained in 3.0), independent of backend-specific types:
boolean
integer
decimal
string
binary
date
time
datetime
duration
list(element_type)
map(key_type, value_type)
object(fields)
tuple(element_types)
Boolean is the DTCS primitive used for predicates. Each value also carries its DTCS value-state semantics: present, null, missing, or invalid. Plugins MUST either preserve a declared type and state or report a capability/type error before execution. Silent narrowing or state collapse is forbidden.
ETLantic authoring aliases such as array and struct MUST normalize to DTCS
list and object. Backend-width aliases such as int64 MAY be accepted by
the facade only when normalization to the DTCS logical type loses no requested
semantic constraint. An unresolved authoring type MAY exist during partial
analysis but MUST NOT remain in a validated executable plan.
5. References¶
5.1 Input references¶
An input node identifies one declared Input[T] port. It MUST contain the
port identity and contract identity. It MUST NOT contain data or a live backend
handle.
5.2 Column references¶
A column reference contains a field name and optional relation identity. Resolution MUST detect missing and ambiguous columns. Qualified names are authoring conveniences; normalized IR uses stable relation identities.
5.3 Parameter references¶
A parameter reference identifies one declared Parameter[T]. Serialized IR
MUST contain its name and type but MUST NOT contain a runtime override or
secret value. Public non-secret defaults MAY appear in the transformation
contract, subject to bounded-literal rules.
5.4 Literals¶
Literals MUST be bounded, typed scalar or recursively bounded collection
values. Callables, modules, classes, backend objects, open resources, arbitrary
Python instances, and SecretValue objects are forbidden.
Implementations MUST provide configurable size, depth, and collection limits. Secret-marked values MUST be represented by references and MUST NOT be encoded as literals.
6. Structured expressions¶
DTCS 2.0 defines five structured expression node kinds:
literalfieldRefunarybinarycall
DTCS 3.0 adds lambda for bounded higher-order expression bodies. Plan v2 MAY
contain lambda nodes; plan v1 MUST NOT. Lambda parameters bind with
fieldRef scope: "lambda" and MUST NOT capture undeclared outer names.
Aliases, sort direction, aggregate context, and window placement are expressed
by their containing action rather than by inventing additional expression node
kinds. The published operators are comparison (eq, not_eq, lt, lte,
gt, gte, null_safe_eq), boolean (and, or, not), arithmetic (add,
subtract, multiply, divide, modulo, negate), membership (in,
between), and access (field, index, element_at).
Every expression MUST have a stable operation or function identifier. Opaque
expressions and repr()-based serialization are forbidden.
7. Relational expressions¶
The DTCS dataset Semantic Actions (2.0 retained; 3.0 adds reshape/set families
via profiles) include project, select, filter,
with_fields, rename_fields, drop_fields, aggregate, group, join,
sort, union, distinct, deduplicate, limit, partition, window, and
derive. Field Semantic Actions are lowercase, uppercase, capitalize,
trim, normalize_whitespace, and hash_sha256.
PySpark-inspired ETLantic names are facade syntax only. For example,
withColumn and withColumns normalize to with_fields; orderBy and
sort normalize to sort; dropDuplicates normalizes to deduplicate; and
unionByName normalizes to union with mode byName. The serialized plan
MUST contain the DTCS action and its registered modes, not the facade spelling.
Nodes MUST reference child expressions structurally. Cycles are invalid. Implementations MUST impose node-count and depth limits during loading and validation.
Actions, writes, collection, display, and resource acquisition are not relational expressions and MUST NOT appear in this IR.
8. Output binding¶
Each declared Output[T] MUST map to exactly one relational expression.
Undeclared output keys and missing declared outputs are invalid. Output role
(valid, invalid, or side) comes from the transformation contract and MUST
remain associated with the output through compilation and reporting.
9. Core semantics¶
9.1 Nulls¶
Portable evaluation distinguishes DTCS null, missing, and invalid value states. Boolean expressions additionally have true and false values. Filtering retains only rows whose predicate evaluates to true. The DTCS registry entry for the filter action defines the treatment of false, null, missing, and invalid predicates; compilers MUST NOT collapse those states.
Equality with null produces null. Authors use isNull() and isNotNull() for
null predicates. Plugins MUST NOT rewrite value == null into is null.
Missing and invalid are not null and MUST NOT be coerced to null unless a standard registry entry explicitly defines that behavior. NaN is also not null. Floating-point NaN behavior MUST be declared separately by each operation whose backend behavior differs.
9.2 Boolean operators¶
and, or, and not operate according to DTCS-registered truth tables that
cover null, missing, and invalid. Plugins MUST NOT use host-language truthiness.
9.3 Numeric operations¶
Numeric promotion follows the least widening type capable of representing both operands within the portable type lattice. Decimal precision and scale changes MUST be deterministic and inspectable.
Integer overflow MUST fail unless an operation explicitly declares widening. Silent wraparound is forbidden. Division by zero MUST produce a declared portable error unless a future semantic mode explicitly selects another behavior.
round uses half-even rounding unless the function explicitly selects another
mode.
9.4 Casts¶
Casts are explicit. A cast is either strict or tolerant:
- strict cast: invalid input fails the operation
- tolerant cast: invalid input produces null and requires explicit syntax
Plugins MUST NOT substitute tolerant behavior for strict behavior.
9.5 Strings¶
String length and substring offsets are defined in Unicode code points, not encoded bytes. Index origins MUST be explicit in the function definition; portable substring APIs use zero-based indexing even if a backend lowering uses another convention.
Case conversion uses Unicode default case conversion. Locale-sensitive behavior requires an explicit future function and capability.
9.6 Timestamps¶
Timestamp values are either timezone-aware or naive. Plugins MUST NOT silently
attach, remove, or change a timezone. Runtime profiles declare the timezone
used by functions such as current_timestamp() and timestamp parsing.
current_timestamp() is stable within one logical run. All occurrences in a
run resolve to the same logical instant.
9.7 Ordering¶
Relational outputs are unordered unless an explicit sort node exists. Sort expressions specify direction and null placement. A plugin incapable of preserving requested null placement MUST reject the plan.
Stable ordering among equal keys is not guaranteed unless an explicit total ordering is declared.
9.8 Joins¶
Join kinds are inner, left, right, full, semi, anti, and cross.
Cross joins require explicit syntax. Null join keys do not match under ordinary
equality. Null-safe equality requires a distinct operator.
Column collisions MUST be resolved or diagnosed before execution. Plugins MUST NOT invent backend-specific suffixes as portable output names.
9.9 Aggregation¶
Grouping treats null grouping keys as one group. Aggregate null behavior is
defined per function. count(*) counts rows; count(column) counts non-null
values. Aggregations over empty input MUST follow the registered function
semantics and output nullability.
9.10 Determinism¶
Operations and functions are classified as deterministic, run-stable, or nondeterministic. Nondeterminism contributes requirements to the plan and MUST affect cache, retry, and idempotency decisions.
10. Validation¶
Before compilation, ETLantic MUST validate:
- protocol version and bounded structure
- port and reference integrity
- column resolution
- expression typing and nullability
- output contract compatibility
- operation and function registration
- plugin capability coverage
- semantic-mode compatibility
- secret and executable-object exclusion
Expected failures produce structured diagnostics with an expression path and stable code. Plugins MUST NOT defer discoverable unsupported-operation errors until execution.
11. Plugin capability contract¶
A plugin support declaration includes:
- supported protocol versions
- DTCS profile identifiers
- Semantic Action identifiers and versions
- function identifiers and versions
- operator identifiers and versions
- supported portable types
- semantic modes and limits
- determinism support
- lazy/eager and ownership behavior
Support is exact. A plugin MUST reject an operation it approximates differently. A compiler MAY optimize or fuse expressions only when observable semantics, security domains, validation boundaries, and logical identity mappings remain unchanged.
The published profile identifiers are:
| Profile | Required semantic family |
|---|---|
dtcs:profile/portable-relational-kernel/1 |
kernel relational actions and scalar expressions |
dtcs:profile/portable-relational/1 |
full relational actions, joins, unions, aggregation, ordering, and deduplication |
dtcs:profile/portable-window/1 |
experimental windows and frames |
dtcs:profile/portable-complex-types/1 |
experimental composite types and access operations |
A plugin MAY advertise individual capabilities without claiming an entire profile. It MUST claim a profile only after passing every required DTCS fixture for that profile. Window and complex-types claims remain experimental until DTCS's two-independent-compiler graduation requirement is met.
12. Security¶
Definition building and planning MUST NOT:
- read source data
- resolve secrets
- contact execution systems
- execute native transformation implementations
- deserialize executable objects
- evaluate arbitrary user-provided code from serialized artifacts
Python decorator authoring imports and invokes trusted definition code with symbolic objects. Static artifact loading reconstructs IR from data only and MUST NOT import or invoke the original definition.
Compilers treat names and literal values as untrusted. SQL lowerings MUST use safe identifier handling and bound parameters. Plans, diagnostics, cache keys, and reports MUST remain secret-free.
13. Compatibility¶
Readers MUST reject unknown major protocol versions. They MAY accept newer minor additions only when every used node and semantic mode is understood.
Adding an optional operation is backward compatible. Changing existing operation meaning, canonicalization, type promotion, or null behavior requires a new major protocol version.
The DTCS plan schema, ETLantic authoring profile, and ETLantic compiler protocol are independently versioned. The shared ETLantic/DTCS publisher MAY coordinate their releases, but implementations MUST still declare and test compatibility for each boundary.
14. Conformance¶
A conformant compiler must pass the shared etlantic.testing portable
transformation suite for every capability it advertises. Conformance covers IR
loading, compilation, execution results, null and error behavior, type
preservation, diagnostics, determinism, security, and bounded failure.