Immutable Pipeline Planning¶
Odibi's immutable planner creates a bounded logical graph from caller-supplied YAML
text. It is selected before PipelineManager.from_yaml, eager runtime imports,
extension discovery, connection/provider setup, and runtime construction.
Only status == "planned" is success. unresolved is a fail-closed result: a logical
graph was projected, but one or more meanings depend on excluded extension, provider,
or runtime state. invalid means no trustworthy plan was produced.
Package API¶
from odibi.planning import plan_pipeline_bytes, plan_pipeline_yaml
response = plan_pipeline_yaml(yaml_text)
if response.status != "planned":
handle_bounded_diagnostics(response.diagnostics)
canonical_json = response.to_json()
response_dict = response.to_dict()
plan_pipeline_yaml accepts an exact str; plan_pipeline_bytes accepts exact UTF-8
bytes. Callers may supply a nominal PlanningLimits instance to lower limits, never
raise the compiled ceilings. Both functions return a frozen PlanningResponse.
CLI¶
The selector is deliberately stdin-only. It does not accept a path, --env, project
root, node/pipeline selector, resume option, or any runtime facility. Standard output
contains exactly one canonical JSON response followed by one line feed.
| Exit | Meaning |
|---|---|
0 |
status == "planned" |
2 |
invalid arguments, UTF-8, YAML, schema, dependency, or limit |
3 |
status == "unresolved" |
4 |
sanitized internal or serialization failure |
MCP and workflows¶
Authorized MCP test_pipeline calls use the same package planner when the compatibility
mode is "dry-run" (the default). validate_yaml_runnable is a deprecated planning
alias. Their planning results equal PlanningResponse.to_dict() with no adapter
envelope. Existing max_rows and dispatcher sample_size inputs remain accepted and
bounded for one compatibility cycle, but are deprecated, ignored, and omitted from the
response.
Authorization remains separate from immutability:
- HTTP planning still requires the exact configured Bearer credential.
- Explicit trusted-local dispatcher/bootstrap planning remains available.
- Stdio planning without HTTP identity remains denied.
- The remote workflow allowlist remains exactly
validate_yaml_simple, whose response remains validation-only rather than planner schema1.0. - Trusted-local planning workflows branch to success only for
status == "planned".
Operation safety ladder¶
| Rung | Exact current surfaces | Effect and decision rule |
|---|---|---|
| 1. Inspect | Static MCP reads such as odibi_execute("search_docs", ...), odibi_execute("get_schema", ...), odibi_execute("list_patterns"), and odibi_execute("list_transformers"); configured/runtime actions map_environment, story_read, node_sample, node_failed_rows, and lineage_graph; CLI odibi graph CONFIG, odibi catalog ..., and odibi story last when prerequisites are met. |
There is no command named odibi inspect. Static reads differ from sensitive/runtime reads; remotely, named runtime-data actions also require managed-project preparation. Catalog and Story inspection is generally post-execution. odibi graph CONFIG is not immutable planning. Check odibi_help(action=...) and the action effect before MCP calls. |
| 2. Validate | CLI odibi validate CONFIG --format json; MCP odibi_execute("validate_yaml", '{"yaml_content":"..."}') and odibi_execute("validate_pipeline", '{"pipeline":"..."}'). |
Checks accepted structure and rules for that validator. It is not execution, immutable planning, provider qualification, or proof of runtime success. |
| 3. Plan | cat CONFIG \| odibi plan --stdin --format json; package plan_pipeline_yaml(text) / plan_pipeline_bytes(raw); MCP odibi_execute("test_pipeline", '{"pipeline":"..."}'); helper/workflow test_pipeline(yaml_text, mode="dry-run"). |
Dedicated bounded logical planning. Only status == "planned" succeeds; unresolved and invalid fail closed. The registered MCP action remains ActionEffect.EXECUTION, so applicable identity/route authorization is still required even though its current implementation is immutable. |
| 4. Legacy simulation | CLI odibi run CONFIG --dry-run; package PipelineManager.run(dry_run=True). |
Compatibility-only late runtime simulation. It may load environment/project code, construct runtime facilities, and cause surrounding lifecycle effects. It is not immutable, no-write, read-only, validate-only, or a security boundary. There is no registered universal MCP dry_run action. |
| 5. Execute | CLI odibi run CONFIG; trusted/local MCP workflow actions run_workflow and resume_workflow only where current route policy permits. |
Accept configured data writes, Story generation, catalog/state/logging/alert effects, and workflow/session behavior. Remote workflow policy is narrower than the effect class; consult the MCP route guidance, current effect help, and authorization. run_workflow is not a general remote arbitrary-pipeline executor. |
Selection rule: inspect relevant information; validate syntax and declared constraints; use immutable planning before accepting runtime effects; use legacy simulation only when its initialization and lifecycle effects are acceptable; and execute only after reviewing configured outputs, repeat mode, privacy, and cleanup.
Schema 1.0¶
Every package, CLI, MCP direct, and trusted-local workflow planning result has exactly this logical shape:
{
"diagnostics": [],
"plan": {
"name": "demo",
"pipelines": [
{
"edges": [{"source": "source", "target": "target"}],
"name": "example",
"nodes": [
{
"depends_on": [],
"kind": "read",
"name": "source",
"resolution": "resolved"
},
{
"depends_on": ["source"],
"kind": "write",
"name": "target",
"resolution": "resolved"
}
]
}
]
},
"schema_version": "1.0",
"status": "planned",
"truncated": false
}
Pipeline and node declaration order is preserved. Dependencies, derived edges, and diagnostics have deterministic canonical ordering. Identifiers are Unicode NFC normalized and reject physical-path/URI-like forms. No physical read/write value, connection/provider configuration, credential, runtime detail, process output, exception text, timing, or host metadata is projected.
Hard bounds¶
| Resource | Maximum |
|---|---|
| UTF-8 input | 1,048,576 bytes |
| YAML documents | exactly 1 |
| YAML nesting depth | 32 |
| YAML scalar/sequence/mapping nodes | 20,000 |
| YAML aliases | 64 |
| Recursive aliases or merge keys | 0 |
| Pipelines | 128 |
| Nodes per pipeline | 1,024 |
| Nodes total | 4,096 |
| Edges total | 16,384 |
| Emitted diagnostics | 256 |
| Logical identifier | 128 Unicode code points |
| Other emitted string | 512 Unicode code points |
| Canonical JSON response | 2,097,152 UTF-8 bytes |
Diagnostics beyond the cap are retained deterministically and set truncated to true.
An oversized response is replaced in full by one fixed RESPONSE_LIMIT_EXCEEDED
invalid response; a partial graph is never emitted.
Boundary and non-goals¶
The planner uses only in-memory caller text, the Python standard library, and PyYAML.
It does not inspect a path or ambient project/temp files, create scratch, load .env,
execute transforms/plugins/entry points, construct connections/providers/credentials,
start Spark, inspect catalogs/state/HWM, generate stories/docs/lineage, emit alerts or
telemetry, touch destinations, launch subprocesses, or create network clients. It does
not mutate CWD, environment, sys.path, sys.modules, registries, logging, active
sessions, the filesystem, or external state.
This is logical planning, not sandboxing arbitrary Python and not runtime semantic
validation. Extension-, provider-, and runtime-dependent meaning produces fixed typed
unresolved diagnostics without executing the excluded facility.
Legacy runtime simulation is different¶
PipelineManager.from_yaml(...), PipelineManager.run(dry_run=True), and
odibi run PATH --dry-run remain callable for compatibility as legacy late runtime
simulation. They may load .env, project/installed code, providers/credentials,
engines, catalogs/state, logging/telemetry, stories/lineage, alerts, and surrounding
lifecycle effects. They are not immutable planning or security boundaries, and planner
failure never falls back to them.
Bounded legacy-documentation follow-up¶
The immutable planner and directly linked CLI/MCP guidance use the contract above. A
repository audit also found historical material that still uses dry-run as shorthand
for βvalidate,β βno writes,β or βwithout executing.β Those statements describe only
ordinary node-operation skipping and must not be interpreted as a process-wide safety
guarantee. The bounded documentation follow-up is to relabel legacy pipeline dry-run
claims in these source documents and their generated MCP corpus copies without changing
the separate catalog/system cleanup-preview commands:
docs/visuals/run_lifecycle.mddocs/guides/{cli_master_guide,mcp_guide}.mddocs/features/{pipelines,engines}.mddocs/reference/{cheatsheet,developer_cheatsheet}.mddocs/{ODIBI_DEEP_CONTEXT.md,playbook/README.md,journeys/senior-data-engineer.md}odibi_mcp/tools/execute.pyandodibi/utils/error_suggestions.py
Until that bounded wording follow-up lands, this page and odibi run --help are the
authoritative distinction: legacy runtime simulation can cause surrounding effects;
immutable planning requires caller-supplied text through the dedicated planner.