-
-
Notifications
You must be signed in to change notification settings - Fork 0
11 Philosophy
Flowline is opinionated. Every design decision traces back to a small set of convictions about what good Dataverse developer tooling looks like. This page makes those convictions explicit.
The source-centric mantra — "source is always the truth, pack from local source and import" — is a story that breaks down in Dataverse. Entities, forms, views, relationships, and workflows cannot be authored from source files. They are created in the environment and snapshotted into source control. Pretending otherwise is the inconsistency.
The arrow of causality points one way. The git repo records what an environment contained at a moment in time — unpacked files are downstream of an edit in the maker portal, not the substrate of authoring. Environment → source, never the reverse.
AI is starting to move authoring off the maker portal — an AI agent can conjure tables, columns, forms and views from a terminal. Today the arrow still doesn't flip: the agent mutates the environment, then you snapshot — a nicer front-end, not source-first authoring, which only sharpens §5. Maybe the data model is the one that is going to switches sides soon — from environment to source 😉.
Flowline embraces a dual-flow model instead:
-
Code assets (plugins, web resources) flow up:
pushdeploys the locally built DLL and web resources to DEV. Source drives Dataverse for these. Plugin steps, step images, and web resources missing from source are deleted on push — the code side is genuinely source-driven. -
Dataverse configuration (entities, forms, views, workflows, relationships) flows down:
syncsnapshots the DEV environment state and commits it to source control. The environment is the truth here. You cannot write entity definitions from files into Dataverse — you capture what the environment already knows.
The critical insight is what happens at deploy time. deploy packs from Solution/src/ — which contains the DLL captured during the last sync, not the current local build. That DLL was pushed to DEV, verified there, and snapshotted back. Deploy promotes what was tested, not what was built last. If you changed code after the last push but before deploy, the tested artifact is what ships.
The source-centric counterargument is reproducible builds: given source, reconstruct the artifact. Valid in general software engineering — but in Dataverse you are already environment-dependent for configuration. Flowline's model is internally consistent: everything flows through DEV. Git sits in the middle as the audit trail of verified states, not the authoring surface for Dataverse configuration.
This is why deploy has no equivalent of pac solution pack's --map option. The solution is already a snapshot — forms, views, and business rules exist only as exported XML, regenerable from nothing. --map says everything in that snapshot is trustworthy except the one component hardest to verify: a form's XML you can diff and read; a compiled DLL you cannot. It swaps the one artifact nobody can eyeball for a binary compiled a second time and never executed. Carving that exception into the snapshot model is backwards, not neutral — see 07-Deploy#why-no-solution-mapping for the concrete mechanics.
DEV is the verification gate. Git is the record.
The mainstream position holds that drift is preventable — keep source control as the origin, forbid manual edits upstream, work "professionally," and you won't have drift. That position makes perfect human discipline a load-bearing component of the architecture. When drift happens anyway — and it does, because makers edit portals, admins hotfix production, flows get toggled, connection references rebind — the framework has no answer except to blame insufficient rigour.
Flowline treats drift as a structural property of a platform where authoring happens in environments, not as a failure to be scolded. The tool's job is to absorb it: refresh DEV from production, detect drift, gate deploys on it. Saying "drift is normal" sounds like admitting sloppiness — which is exactly why the position goes undefended. It shouldn't be.
This is why production is the real master branch. provision copies prod → dev/test/uat and refuses to overwrite a production environment — the master branch is read-only by construction. provision is the branch-from-master operation, DEV is a working copy refreshable from master on demand, and push → sync → deploy is the promote-back path. Flowline doesn't merely tolerate prod-as-master; it's the only direction the tool permits environment state to flow.
A solution's version is metadata inside the snapshot — exported from the environment alongside the forms and views. That leaves two moments you could assign it: at export, in the environment, the way the platform does; or at pack time, stamped into build output on a CI runner. Flowline versions at export — sync bumps the version in DEV before pulling the snapshot down, so the committed Solution.xml is the shipped version. Both come from one snapshot, so the repo can't lie about what shipped.
Export-time is what the platform itself does. The maker portal pre-increments the version on every export; Power Platform Pipelines mints a new version at deploy — which is itself an export step. Both set the number in the environment, at the moment the artifact is produced. Flowline's sync sits squarely in that camp.
Pack-time versioning is the one that fights the grain. The version lives in several files at once — Solution.xml, the assembly, control manifests, the .dll.data.xml sidecars — so stamping it late means rewriting all of them in CI, usually by regex against a build counter. Microsoft's own reference sample does exactly this across five file kinds and still gets it wrong in three (the NuGet version pinned at 1.0.0.0, IntroducedVersion drifting, an identity bump orphaning plugin steps); an entire custom MSBuild SDK exists only because passing the version at compile time doesn't natively work. Flowline avoids every bit of that by never assigning the version late. Flowline flows with Dataverse; pack-time versioning swims against it.
This is the most opinionated stance in the tool, and it is held deliberately.
The Dataverse community has long treated unmanaged-in-production as amateurism. Flowline exists partly to disagree — loudly. The problem was never the package type. It was the tooling: without proper Git tracking, orphan cleanup, DTAP gates, and deployment discipline, unmanaged solutions drift and become unmanageable. With those things, they don't.
Prod-as-master (§2) is itself an argument for unmanaged. Branching from production means copying a live environment into a fresh dev environment and continuing to work there — and that only works if the components stay editable. Unmanaged components do. Managed components are locked in the target environment: copy a managed production into a new dev environment and you cannot freely edit what you find, because the authoring source lives elsewhere. Choosing managed for a customer solution forfeits the branch-from-prod workflow the whole model is built on.
Flowline's orphan cleanup, Git-tracked solutions, clean diffs, and promotion gates are a direct answer to every legitimate argument for mandatory managed solutions. Managed deployment is fully supported — but it is never the default posture, and it is never presented as the "serious" option for the customers' own customizations.
Plugin registration intent lives in committed source: [Step], [Filter], [PreImage], and [PostImage] attributes on the plugin classes, and flowline:onload / flowline:onsave / flowline:onchange / flowline:tabstatechange / flowline:onreadystatecomplete annotations beside the code they wire. The source file is the registration record. The org holds a projection of it, never the original.
The pattern underneath this is Locality of Behavior (LoB) — the behaviour of a unit of code should be obvious from reading that unit alone, not reassembled from a separate registration tool, an spkl.json, or the org. A [Step] attribute sits on the plugin class it registers; a flowline:onload annotation sits in the handler it wires. You see what fires and when in the same place you see what runs — no jumping to the Plug-in Registration Tool to learn that this class is a post-create step. Flowline is a deliberate fan of LoB and applies it wherever the platform allows: intent and code share a location, so the code explains itself.
That turns a class of hazards into non-events. Delete a plugin assembly and recreate it, and every step and image is re-derived from the attributes on the next push. Nothing is orphaned because nothing was ever only-in-the-org.
This matters more with AI in the loop. The alternative is imperative registration — a human or an agent issuing commands that mutate org state. The org ends up correct; the reasoning that produced it survives nowhere. A generated script records what was done, not what was meant, and re-running an agent is non-deterministic — you may get a different, plausible, subtly wrong set of commands, and the repo can't adjudicate which is right because it never held the intended state. Declarative source-resident intent fixes all of it: the file is the desired state, reconciliation is deterministic, drift is a diff, and review is reading a declaration. The agent helps you write the attribute — but the attribute, not the transcript, is what survives and what reconciles.
The general principle: put the intent in the artifact you already version, and reconcile toward it. Anything an agent or a human had to do to make the org correct is a step you will have to re-do, re-derive, or re-trust later.
Never stated as a slogan, but present in every feature.
Plugin step behaviour is derived from class names — AccountPostCreatePlugin maps to PostOperation, Create, account, sync without any config. Web resource dependencies are inferred from base-name matching and in-source // flowline:depends annotations. No spkl.json. No dependency config file. The folder structure is prescribed and discoverable by convention.
The solution file (.sln/.slnx) is the folder config: Flowline discovers all three projects — solution package, plugins, web resources — by reading the solution file, so there's no Paths block in .flowline. No second place to state what the solution file already states, and no drift risk between the two.
The constraint that keeps this honest: convention must still be obvious to the developer. Convention without discoverability is just magic. Flowline makes its conventions visible through naming, folder structure, attribute documentation, and verbose output that shows exactly what was inferred.
When PAC CLI appeared as Microsoft's official Power Platform CLI, the decision was clear: delegate auth, solution import/export, and environment management rather than own them.
Flowline is a workflow layer, not a platform. It does not re-implement what PAC CLI already does well. When PAC CLI improves, Flowline benefits automatically. This is a deliberate bet on staying thin and composable rather than comprehensive and self-contained — the right trade-off for a community tool built on top of an actively developed official CLI.
Where PAC has no answer — attribute-driven plugin registration, Git-based push loop, sync summaries, WebResources scaffolding, AI-native schema context — Flowline fills the gap with its own implementation.
Every destructive operation supports --dry-run. DTAP promotion gates enforce environment order and block deploys that would skip steps. Error messages include the corrective command verbatim — not just what went wrong, but exactly what to run next. Structured exit codes follow de facto CLI conventions (curl, git) so agents and scripts can act on them without parsing text.
The consistent goal: the developer knows exactly what will happen before it happens.
But the tool never paralyzes. Every guard has an explicit escape hatch — --force, --no-delete, --skip-dtap-check. The posture is protect by default, trust explicitly. A developer who knows what they are doing can always override; a developer who doesn't is protected from unintended consequences.
The tone-of-voice guide is a philosophy document disguised as a style guide: talk like a smart colleague who knows their stuff and doesn't waste your time. No preamble, no hedging, no "attempting to." Verbose mode shows exact commands to copy-paste, not summaries of what happened.
This extends to the attribute documentation: [Step] explains why each pipeline stage exists — Validation runs before the transaction opens, Pre is inside the transaction, Post is after the save but inside the same transaction and PostAsync is after the committed transaction. The tool treats developers as capable of understanding the Dataverse execution model if it's explained well once, then gets out of the way.
Complexity is never hidden. It is explained clearly, then removed from the daily path.
An AI agent is a first-class user of Flowline, not a bolt-on. The design question was never "can we add AI features?" but "can an agent drive this tool as confidently as a human, without extra prompting or guessing?" That conviction shapes the surface rather than decorating it — an agent that can't tell what happened can't be trusted to act on it.
Everything else follows from that. The tool speaks in structured exit codes an agent can branch on instead of parsing prose; errors carry the exact command to run next; the repo stays honest about its own state, regenerating DATAVERSE_CONTEXT.md and AGENTS.md on every sync rather than leaving a stale opt-in. Flowline even ships as an installable agent plugin for Claude Code and Codex, so the agent knows how to drive it before you've cloned anything.
Clone is explicitly safe to re-run on an existing project. AGENTS.md and CLAUDE.md writes both check before writing — existing content is never overwritten. AGENTS.md mutation (schema context section) is append-only. Push converges Dataverse to source state on every run. DATAVERSE_CONTEXT.md self-heals on every sync.
Idempotency is chosen repeatedly as a design constraint across unrelated features. The belief behind it: the tool should be safe to run at any time, in any order, without requiring the developer to track what has already been done. A command that is safe to run twice is a command you can run with confidence.
Flowline believes the Dataverse developer deserves tooling that respects their intelligence, protects their work, stays out of their way, and gives them full confidence before anything touches a live environment. Source control is the centre of gravity, not the Maker Portal. Unmanaged can be professional if the tooling is right. And it is building deliberately for a world where AI agents are first-class participants in the development loop.
One rule generates most of the above: a convention imported from a context where it was correct, applied to Dataverse where its premise doesn't hold, then defended as a principle rather than re-derived as a trade-off, is worth re-examining. "Never commit binaries," "source is the truth," "drift means you were sloppy" — each was earned somewhere else and carried here without adapting it. The point isn't that abstraction or discipline is bad; it's that a premise borrowed from elsewhere has to be re-checked here before it earns the name principle. Flowline's opinions are that re-derivation, done out loud.