RFD 0035 — The composable operator-tree execution pipeline
- State: discussion
- Opened: 2026-06-15
- Decides: the realization of RFD 0020’s composable pipeline (D2/D4/D9) that RFD 0021 D1 deliberately reserved — “the optimizable
LogicalPlanIR is reserved for [an operator-tree] executor when graph-native physical operators + factorization genuinely demand it.” That consumer has arrived (RFD 0036: foreign-source federation + a relation-valued compute operator + a federation-split optimizer rewrite, none of which has a home in the currentCompiledRule-direct path). This RFD builds the operator tree as the single shared lowering target for every front-end, a tree-level optimizer, a physical mapper, and a generalized operator-call executor contract — while preserving the proven semi-naive evaluator (RFD 0021) as the physical operator for the recursive/conjunctive core. It also introduces the relation-valued (table) operator IR — the relation→relation node absent fromCompiledAtomtoday — co-designed with RFD 0029’s aggregate surface.
This RFD is co-designed with RFD 0036 (heterogeneous stores), which is its forcing consumer; 0035 is the engine layer, 0036 is the store layer that lands on it. It is Lean-first where it touches reasoning semantics — the executed meaning conforms to spec/lean/Argon/Reasoning/ (Fixpoint.lean, Datalog/Compiled.lean) and is held there by the differential oracle (RFD 0021 D8); the pipeline structure, operators, optimizer, and mapper are engine architecture the Lean does not mechanize (per AGENTS.md scope), settled from first principles here. It commits a plan, folded into the implementing PRs per the repo’s discussion-first practice.
Reconciled with the performance / distribution / consensus research campaign (2026-06-15). D4/D6/D7/D8 below are updated to record the campaign’s findings: the columnar content-addressed segment + IVM maintainer is the primary read-model (D7, a priority inversion — the architecture was already correct, only its sequencing was set without performance data); the analytical/columnar tier is Argon’s own native vectorized engine, not a delegated one (D4/D6 — Argon is the high-performance engine, never a “dumb” forwarder); and the IVM↔oracle equivalence joins the frozen-EDB theorem as a named obligation (D8). The campaign is research and decides nothing; these edits are the cut, settled in discussion.
Question
Argon’s runtime engine has, by deliberate staging, two halves that don’t meet. The reasoner (oxc-reasoning) is a fast, correctness-first semi-naive evaluator over CompiledRule — indexed + persistent arrangements, worst-case-optimal joins on cyclic bodies, CSR index-free adjacency, factorized aggregates, all anchored to a differential oracle (RFD 0021). Above it sits a designed-but-unwired operator-tree pipeline (logical/, optimizer/, physical/, runtime/operators.rs) that RFD 0021 D1 chose not to wire, on a “build-correctly-once” argument: a LogicalPlan that merely round-trips back into the CompiledRule executor is throwaway scaffolding until a real operator-tree executor exists.
That argument was right, and it carried an explicit trigger condition: the tree gets built when graph-native physical operators + factorization genuinely demand it. RFD 0036 is that demand, and sharper than anticipated:
- a foreign-source scan is a new leaf the optimizer must rewrite filters/projections into (pushdown);
- federation-split
(absorbed, remainder)is a tree rewrite with no home in a flat rule body; - a relation-valued compute operator (e.g. k-means over a columnar securities master) reads a relation and returns a relation — no
CompiledAtomvariant expresses this (the five variants arePredicate,Comparison,Naf,Compute= scalar map,Aggregate= relation→scalar; verifiedcompile/rule.rs:233-283); - binding-limited foreign sources need magic-sets/demand — a tree-level transformation.
What is the execution pipeline that hosts all of this — for declared rules, ad-hoc queries, checker goals, and mutations alike — without throwing away the proven evaluator, and without becoming the monolith nous warned against?
Context
Verified current state (against origin/main @ 1bdfa16)
LogicalPlanis orphaned, relational-core only.logical/mod.rs:21—Scan/Filter/Map/Join/AntiJoin/Distinct/Recurse/Sink, eachTier-tagged;Filter’s predicate is opaque CBOR “pending the CoreIR expression interpreter.” No graph-native, mutation, compute, or foreign nodes. Never instantiated by any front-end (oxc-reasoning/AGENTS.md:24-32landmine note).- The front-ends bypass it entirely. All compilation goes
AtomIR → compile::compile_rule → Engine::evaluatedirectly — verified at ~11 sites inoxc-runtime(checks.rs:628,644;lib.rs:1211,6807+ the.evaluate(sites6819/7002/7086/7113/7147/7306/7402/7484/7527/7813/7867).Engine::evaluate(oxc-reasoning/src/lib.rs:144) reorders each body (SIP) and dispatches to aTierExecutor. TierExecutor::executeis whole-program.executor/mod.rs:67-85—execute(&self, rules: &[CompiledRule], catalog: &mut RelationCatalog, policy: ConvergencePolicy), writes derived facts into the catalog in place.SemiNaiveExecutoris the only real impl (coversStructural/Closure/Recursive);SLG/DBSP/SMT/Kripke/Koraare docstring stubs.- The optimizer that runs is one pass over
CompiledRule.optimizer/reorder.rs— SIP/bound-set reorder + cardinality tie-break (RFD 0021 D3); it tracksbound: BTreeSet<VariableIdx>(reorder.rs:61) and consumes catalog sizes (reorder.rs:189-195). TheOptimizerPipelineoverLogicalPlan(optimizer/mod.rs) is part of the dead family. runtime/operators.rs(map/filter/join/antijoin/distinct/integrate/differentiate) is the dead Z-set operator vocabulary — the semi-naive loop implements joins inline; it marks the future IVM boundary.- The catalog is rebuilt per query.
query_derive → materialize_predicates → RelationCatalog::new(); theStoreholds no materialized catalog (RFD 0021 D7). ACatalogEntrycarries atier+ aRelation<Vec<u8>> = BTreeMap<Vec<u8>, Weight>(catalog/mod.rs:70,runtime/relation.rs). - The freeze discipline already exists — the wall clock is frozen for the duration of
evaluate_to_fixpoint(eval.rs:170-174); stable relations’ arrangements are held across iterations (eval.rs:232-235).
What RFD 0021 established (and we keep)
RFD 0021 D1 made CompiledRule the executed form and the “operator pipeline” the set of operators the evaluator runs, deferring the operator-tree executor to its real consumer. D6 holds every operator a pure Z-set→Z-set function so IVM is an additive outer loop. D8 makes the semi-naive/binary evaluator the differential oracle for every optimization. These are load-bearing and survive intact.
Decision
Build the operator-tree pipeline RFD 0021 D1 reserved, as the orchestration+optimization layer above the preserved evaluator. Eight decisions.
D1 — LogicalPlan becomes the single shared lowering target (RFD 0020 D2 realized)
Every front-end — ad-hoc query, declared rule, compiler/type-checker goal, and mutation — lowers into one LogicalPlan. The ~11 direct compile_rule → Engine::evaluate call sites are replaced by lower-to-LogicalPlan → optimize → map → execute. CompiledRule is reclassified as the physical form of a Datalog-fixpoint sub-plan (the mapper’s output for Recurse/conjunctive nodes), not a front-end target. This is precisely the inversion RFD 0021 D1 said to perform “when a consumer demands the tree”: the tree is no longer scaffolding because it now carries front-ends a flat rule body cannot (foreign scans, table operators, federation rewrites, checker goals).
D2 — The evaluator is preserved as the fixpoint physical operator; the tree is coarse-grained
The proven eval.rs (WCOJ, CSR, persistent arrangements, factorization, oracle-validated) is not reified into per-join boxed operators. It is the physical implementation of a Recurse/conjunctive Datalog node. The operator tree reifies inter-operator / source / compute / recurse / sink structure; within a Datalog-fixpoint node the fused evaluator runs unchanged. Rationale: fine-grained reification would regress the tight semi-naive loop and dissolve the WCOJ/CSR/factorization fusion the oracle proved correct — discarding RFD 0021’s investment for no gain. Coarse reification honors D1’s “no throwaway” and keeps the engine.
D3 — The frozen-EDB materialization discipline is the composition mechanism
A non-Datalog sub-plan — a foreign scan (RFD 0036 D3), a relation-valued table operator (D4) — is realized by materializing its result into the RelationCatalog as a frozen EDB, after which the fixpoint operator consumes it natively. This generalizes the existing today()-pin (eval.rs:170-174) and stable-arrangement (eval.rs:232-235) discipline from a scalar/relation to any externally-produced relation, and it is the reason cross-source joins need no new physical join operator in v1 — the foreign/computed slice becomes an ordinary CatalogEntry and the existing evaluator joins it as it joins any EDB.
Soundness. The mechanized fixpoint operators range over Interp Atom = Set Atom and never inspect provenance — TP (Datalog/Program.lean:73) and gamma (the GL-reduct least model, Program.lean:107); the compiled engine’s immediate-consequence step equals TP over its grounding via the bridge theorem fire_eq_TP (Datalog/Compiled.lean:232), so provenance-freedom carries to the executed form. A frozen externally-produced atom is therefore semantically indistinguishable from a native one. The only Lean obligation is a semantics-preservation theorem — “a frozen slice injected as an EDB yields the same model as a native EDB of the same extent” — statable against Fixpoint.lean/Compiled.lean with no new machinery (a net-new C12 deliverable; the static case is immediate, the computed case — a frozen relation whose production is itself an inner fixpoint — is the one that needs the statement, see D4 and RFD 0036 D4).
D4 — The relation-valued (table) operator IR, co-designed with RFD 0029
Introduce the missing lowering target: a logical node (Apply / TableOp: relation(s) → relation) and a matching physical contract. It is the home for graph algorithms, windowing/ranking, and foreign analytical compute (RFD 0036’s k-means). It is co-designed with RFD 0029’s aggregate surface: an aggregate (relation→scalar) is the degenerate codomain of a table operator (relation→relation); the two surfaces share one design so a second seam cannot drift from the first. This is a language-level capability (table operators are wanted independently of federation), not federation plumbing.
A table operator’s physical realization is routed by the mapper (D6) to a tier executor; its result is materialized as a frozen EDB (D3). Determinism gate (net-new): a table operator admitted into a fixpoint position must be deterministic-given-its-frozen-inputs. The gate has two faces, and the distinction is load-bearing: for an in-engine operator it is statically checked (the engine sees the operator’s definition); for an opaque foreign compute provider Argon cannot verify determinism by inspection — it is contract-asserted (the connector declares it, as Soufflé functor-purity is author-asserted), and an operator that does not declare determinism is refused in a fixpoint position (RFD 0036 D3). Non-deterministic production (k-means training; any stochastic operator) runs outside the fixpoint and contributes only a frozen, content-addressed artifact; the deterministic re-entry (predict/assign) is what enters the fixpoint (the train/predict split — RFD 0036 D3). A non-deterministic operator in a fixpoint position is refused, never silently admitted (C9). The well-posedness order is fixed: this atom kind gates the executor contract (D6) and the freeze theorem (D3) — there is nothing to specify for an operator that cannot be named in the IR.
The analytical tier is Argon’s own native engine, not a delegated one (updated per the performance campaign, 2026-06-15). The table-operator / analytical-tier executor — vectorized columnar scan / filter / join / aggregate over the segment read-model (D7) and over foreign columnar sources — is built as Argon’s own native physical operators (extending RFD 0021’s engine). An off-the-shelf engine (DataFusion, DuckDB, Polars) is not Argon’s analytical engine; at most its TableProvider is a connector interface shape (RFD 0036 D3), or a clearly-temporary operational bridge — never the permanent execution engine. Two grounded reasons our own engine is forced, not merely preferred: (1) the segment must carry PosBool(M) why-provenance + the Governatori proof_tag + a BitemporalExtent inline (RFD 0036 D7), which every off-the-shelf columnar engine — provenance-free — structurally cannot; (2) the campaign’s measured result is that vectorized-vs-compiled is a small constant and the real cliff is representation (columnar), so a native vectorized engine lands within a small constant of DuckDB/DataFusion while owning the whole stack (provenance, the 4-axis segment, the Lean-conformant fixpoint). The CP3 cut is non-negotiable in either case: a SQL-style linear recursion engine cannot host Argon’s stratified semi-naive fixpoint, so Recurse/AntiJoin/Distinct/WFS never leave the native core.
D5 — The optimizer moves onto the tree
The live SIP/bound-set reorder (reorder.rs, over CompiledRule) is lifted to operate over LogicalPlan, preserving its bound-set propagation (bound: BTreeSet<VariableIdx> — the binding-pattern substrate RFD 0036 D3/D4 consume) and its cardinality tie-break (RFD 0021 D3). New passes land as semantics-preserving tree rewrites: predicate/projection pushdown (lifting RFD 0021 D3’s projection-collapse to the logical layer), magic-sets / demand transformation (the bounded-demand substrate RFD 0036’s binding-limited foreign sources need), federation-split (RFD 0036 D3 — folding Filter/Map into a ForeignScan leaf with a three-valued verdict), and stratum split + tier assignment. The OptimizerRule / OptimizerPipeline traits (optimizer/mod.rs) are made real. Every pass is a semantics-preserving rewrite, enforced as a differential-test obligation (D8): optimized plan ≡ unoptimized plan on generated inputs.
Hard prerequisite — the logical-layer expression interpreter. LogicalPlan::Filter carries its predicate as opaque CBOR today (logical/mod.rs:28, “pending the CoreIR expression interpreter”). Predicate pushdown, federation-split, and table-operator predicates all require a predicate the optimizer (and a connector — RFD 0036 D3 apply_filter) can inspect; an opaque blob cannot yield a three-valued pushdown verdict. So building the logical-layer expression interpreter that replaces the opaque CBOR is a sequencing prerequisite for these passes — not a deferrable open question. It lands before federation-split (RFD 0036 D3) is more than a stub.
D6 — The physical mapper (1:N) + the generalized operator-call executor contract
A physical mapper lowers an optimized LogicalPlan to a PhysicalPlan — per-node physical-operator choice + per-node executor assignment. The whole-program TierExecutor::execute(&[CompiledRule], …) (executor/mod.rs:79) generalizes to an operator-call / sub-plan dispatch so that a Recurse/conjunctive node routes to the semi-naive (later SLG) executor; a Scan to a catalog read and a ForeignScan to a connector (RFD 0036 D3); a table-operator node to a compute/analytical tier executor (RFD 0036 D3). Tier stays compile-time metadata on nodes (the Tier tag LogicalPlan already carries), never a runtime profile flag — the nous anti-pattern (RFD 0020 D9). The existing SemiNaiveExecutor is the first physical executor under the generalized contract; the SLG/DBSP/SMT/Kripke/Kora stubs slot in unchanged in shape. This is RFD 0003’s TierExecutor seam, generalized from “reasoner backends” to “any physical-plan node.”
Placement as a permanent per-workload router (updated per the performance campaign, 2026-06-15). Per-node executor assignment composes with [placement] (RFD 0036 D6): a node’s workload is routed to the store/engine that best serves it. Argon’s own engine is the first-class default; a specialized external store/engine is chosen when it is genuinely optimal for that workload (sub-ms operational point-lookup, a >TB columnar source Argon does not own). This dual stance — Argon is a first-class engine and a permanent orchestrator over heterogeneous stores — is permanent architecture, not scaffolding (RFD 0036 Decision lead): different workloads require different stores now and forever, while Argon itself stays a high-performance engine that never pawns off its own core.
D7 — The columnar content-addressed segment + IVM maintainer is the primary read-model; the frozen path is its correct fallback
Every operator stays a pure Z-set→Z-set function (RFD 0021 D6), so IVM is an additive outer loop — and that purity is exactly what makes the read-model cheap to wire and what keeps it bit-identical to the oracle.
The primary read-model (updated per the performance campaign, 2026-06-15). The persisted read-model is a columnar, content-addressed, immutable segment, maintained incrementally by the currently built-but-dead DBSP operators (runtime/operators.rs — integrate/differentiate/distinct, zero forward-path callers today) so that a mutate produces a delta, not a full catalog rebuild (today every mutate evicts the cache and re-runs the whole fixpoint). This is the convergence point of the campaign’s single-node-execution, storage, and IVM findings — one artifact seen from three angles — and it is the highest-leverage performance work, single-node-meaningful before any S3 or distribution exists. The single-node cliff the campaign measured is row-at-a-time over BTreeMap + per-tuple CBOR decode, which a columnar segment captures most of independent of execution model; so the segment is columnar (decode-once, keep-decoded, keyed by content_id). Retraction (statute sunsets, corrections) is mandatory for Argon and is what forces a real IVM algorithm — the algebra is settled by the prior IVM trilogy (DRedc / two-semiring DBSP; the maintenance loop + segment contract is the work, not the algebra; see RFD 0036 D7/D9). This is Argon’s own engine’s read-model, built natively (D4/D6), not delegated.
The frozen path is its correct fallback, not the headline. Lower → optimize → map → execute with non-Datalog inputs frozen-materialized per query (D3) is the correct fallback the segment is maintained against (the RFD 0021 D8 “genuine mechanism + correct fallback” discipline). What would be hollow is a LogicalPlan that round-trips into the old path, or federation that only works on an inert read-model — we ship neither. (An earlier draft of this RFD called frozen-per-query “the complete mechanism” and IVM “a named subsequent optimization”; the campaign inverts that priority while keeping the architecture — the Z-set purity here is precisely what makes the inversion free.)
D8 — Correctness methodology carries over: the differential oracle is load-bearing
The semi-naive/binary evaluator stays the differential oracle (RFD 0021 D8). Every optimizer pass (D5) and every physical mapping (D6) is diff-tested identical to the unoptimized/oracle path on generated inputs; the pipeline’s own correctness is a test, not an argument. No optimization ships without the oracle. Library discipline unchanged: no unwrap/expect/panic, BTreeMap/BTreeSet only, fmt + clippy clean, cargo nextest-green.
The IVM↔oracle equivalence obligation (added per the performance campaign, 2026-06-15). The incremental read-model maintainer (D7) introduces one net-new proof obligation, the CP3 hinge: maintaining the read-model M under a committed delta Δ must yield exactly the least fixpoint over the original EDB extended by Δ —
maintain(M, Δ) ≡ lfp T_P (E ⊎ Δ)
— provenance- and time-free, on every mutation. The maintainer’s internal (time, diff, iteration) bookkeeping is maintenance state, not meaning; distinct projects the timestamped trace down to the reference Set Atom semantics (Program.lean:73). v1 discharge is the differential oracle: the maintainer’s output is diff-tested identical to a full semi-naive recompute over generated mutation sequences (the same discipline that proves wcoj ≡ binary). The Lean-level theorem joins the frozen-EDB preservation theorem (D3) as a named obligation, statable against Fixpoint.lean/Compiled.lean. No prior IVM system has this theorem because none maintains against an external reference semantics; provenance is preserved through the maintainer by the two-semiring split, so the distinct collapse and the provenance DNF do not fight (RFD 0036 D7).
Rationale
- The trigger condition is met, not invented — and this RFD stands on its own. The table operator (D4) is wanted independently of federation — graph algorithms, windowing/ranking, ML all need a relation→relation node that
CompiledAtomlacks — so 0035 has standalone motivation even while RFD 0036 is still in discussion. RFD 0021 D1 reserved the operator tree for the consumer that genuinely needs it; RFD 0036 is a (sharp) consumer, but not the only justification. Building it now is the staging RFD 0021 designed for — not a reversal. - Preserve the engine, build the layer above it (D2/D3). The freeze-into-catalog discipline lets the operator tree be an orchestration/optimization layer that produces frozen EDBs, leaving the proven fused evaluator as the fixpoint physical operator. We get the tree’s expressiveness without discarding RFD 0021’s WCOJ/CSR/factorization/oracle investment.
- One IR, several physical backends (D1/D6). Unifying the logical layer is what lets one engine serve rules, ad-hoc, checker goals, and mutations coherently; specializing the physical backends (semi-naive, connector, compute tier) is what keeps each role fast. Share the meaning, specialize the mechanism (RFD 0020 D3).
- The table operator is a language gap, not a federation gap (D4). Relation→relation operators (graph algorithms, windows, ML) have no lowering target today; designing the IR coherently with RFD 0029’s aggregates is the honest fix and prevents a second drifting surface.
- Correctness stays a test (D8). Anchoring every pass and mapping to the oracle is what made RFD 0021 transformable under load without regressions; the same discipline is why the pipeline can be built correctly-once.
Alternatives considered
- Wire a
LogicalPlanthat round-trips into theCompiledRuleexecutor. Rejected — RFD 0021 D1’s original reason holds: throwaway the moment a real operator-tree executor exists. We build the executor (D6), not a round-trip. - Fine-grained operator reification (every join a boxed operator;
runtime/operators.rsmade live for evaluation). Rejected (D2): regresses the fused semi-naive loop and dissolves the WCOJ/CSR/factorization fusion.runtime/operators.rsstays the IVM-boundary vocabulary (D7), not the query-evaluation path. - Keep the direct
compile_rulepath; bolt federation onto it. Rejected: a flat rule body cannot host a foreign-scan leaf, a federation-split rewrite, a table operator, or a checker goal — it is exactly the monolithnouswarns against. - Treat the persisted read-model + IVM as a someday-optimization. Rejected after the performance campaign (D7): the columnar content-addressed segment + IVM maintainer is the primary read-model and the highest-leverage work; frozen-per-query is its correct fallback, not the headline. The architecture (pure Z-set so IVM is additive) was already right; only the priority was wrong.
- Delegate the analytical tier to an off-the-shelf engine (DataFusion/DuckDB as Argon’s execution engine). Rejected (D4): forced by the inline-provenance segment requirement and unjustified by perf (vectorized-vs-compiled is a small constant). Off-the-shelf engines are a connector shape or a temporary bridge, never Argon’s own engine.
- Cascades optimizer from day one. Deferred (RFD 0020 D8): rule-based tree passes (D5) suffice until the plan search space justifies a memo/cost-model engine.
Consequences
- Code structure.
logical//optimizer//physical/become the real pipeline; the front-end call sites inoxc-runtimere-point to lower-to-LogicalPlan;reorder.rsis lifted ontoLogicalPlan;TierExecutorgeneralizes to operator-call dispatch; a new table-operator logical node +CompiledAtom/physical contract lands (D4);SemiNaiveExecutorbecomes the fixpoint physical operator under the generalized contract.eval.rsand its oracle stay. - Lean / conformance. The IR’s reasoning semantics conform to
spec/lean/Argon/Reasoning/; the frozen-EDB semantics-preservation theorem (D3) is a net-new, statable obligation. Pipeline structure/optimizer/mapper are engine architecture (outside mechanized scope) and are conformance-tested against the semi-naive oracle (D8). - Spec / reference. An engine-architecture chapter (
spec/reference/src/{17,19}) lands once the pipeline is built; RFD 0021’s “as-built engine” framing is contextualized as the physical layer beneath this logical layer. - Coordination. The
RelationCatalogpublic-API seam (RFD 0021) stays the boundary with the write-path track; RFD 0036 lands its connector/compute/store layers on D3/D4/D6.
Open questions / tracked-future
- The exact table-operator IR shape and its coherence with RFD 0029 — the logical node, the
CompiledAtomvariant, and the shared aggregate↔table-operator design. The novel core; gates the executor contract and the freeze theorem (D4). - The generalized operator-call executor contract signature — how
TierExecutormoves from whole-program to sub-plan/operator dispatch without losing the per-stratum stratification it does internally today. - Checker goals as
LogicalPlan— whetheroxc-checkissues boundedLogicalPlangoals in this RFD or is designed-for (RFD 0020 D3 role 3 is design-for-now; the seam is D1). - Cost model / statistics — cardinality/selectivity over a mutating graph for the reorder + future federation-split + eventual Cascades (RFD 0021 left this open; RFD 0036’s foreign sources have no in-engine cardinality, sharpening it).
- The magic-sets / demand interface RFD 0036’s binding-limited foreign sources consume (D5) — its precise shape (demand-stratify vs monotone bounded re-consultation) is settled with RFD 0036 D4.
- Logical-layer expression interpreter — not an open question but a stated prerequisite (D5): the opaque-CBOR
LogicalPlan::Filter.predicatemust become an inspectable expression before pushdown / federation-split / table-operator predicates work. What remains genuinely open is only its expression coverage (which operators/forms the logical layer interprets vs. defers). - The IVM maintainer’s checkpoint cadence (D7) — how often a mutation mints a new immutable segment: per-mutation (segment churn + GC pressure) vs batched (the in-memory materialization must then survive restart some other way, reintroducing a durability seam). A genuine open the loop surfaces; settled with RFD 0036 D9.
- Enforcing the CP3 cut if an off-the-shelf engine is ever used as a bridge (D4) — whether confining it to non-fixpoint plans is a structural guarantee (fixpoint operators unrepresentable in the lowered sub-plan) or advisory
Tiermetadata; and the provenance-injection step a foreign analytical result needs to re-enter the provenance-carrying fixpoint (the frozen-foreign-EDB marker, RFD 0036 D7).