ctlflow

Source layout

Source directories and corresponding Node, C# and Rust packages.

Source reference: docs/architecture/native-layout.md at 52bf0279

Node, C# and Rust implement the same three products. Selecting an implementation does not change the UI, wire contracts, database or product responsibilities. The tree below shows shared files and language-specific code.

Source tree#

services/
  crossfire/
    apps/web/                         one React application
    database/
      management/sqlite/
        migrations/                   Knex only
        seeds/
    knexfile.management.mjs
    node/
      apps/server/
      packages/
      tests/
      scripts/
      tools/
      Dockerfile
    csharp/
      apps/Crossfire.Server/
      packages/
      tests/
      build/
        Crossfire.EfCompiler/          design-time query generation
        Crossfire.EfHost/              design-time host
      scripts/
      Dockerfile
    rust/
      apps/server/
      packages/
      tests/
      scripts/                        native container package construction
      Dockerfile
    tests/contracts/                  implementation-independent assertions
    scripts/                          shared asset/schema generators
  fleetd/
    node/{apps/daemon,packages,tests,scripts,tools}/
    csharp/{apps/Fleetd.Daemon,packages,tests}/
    rust/{apps/daemon,packages}/
    tests/contracts/
  fleet/
    node/{apps/cli,packages,tests,scripts}/
    csharp/{apps/Fleet.Cli,packages}/
    rust/{apps/cli,packages}/
    tests/contracts/
scripts/                              workspace/build/verification only

Rust integration tests live in their crates. C# uses .NET project, namespace and file conventions. Corresponding packages have the same responsibilities but need not have identical file names.

Semantic package map#

ResponsibilityNodeC#Rust
Primitive results, identity and timekernelCrossfire.Kernelkernel
Rules and repository portsdomainCrossfire.Domaindomain
Wire validation and viewscontractsCrossfire.Contractscontracts
Private files and path validationadapter-filesystemCrossfire.AdapterFilesystemadapter-filesystem
REST client and transportclient + native HTTPCrossfire.AdapterHttpadapter-http
Matrix HTTP integrationadapter-matrixCrossfire.AdapterMatrixadapter-matrix
SQLite connections, query metadata and transactionsadapter-sqliteCrossfire.AdapterSqliteadapter-sqlite
Calendar schedulingadapter-scheduleCrossfire.AdapterScheduleadapter-schedule
Pure listener/runtime coordinationagent-coreCrossfire.AgentCoreagent-core
Journals, delivery confirmation and restart stateadapter-runtimeCrossfire.AdapterRuntimeadapter-runtime
Codex integrationadapter-codexCrossfire.AdapterCodexadapter-codex
Earendil Pi integrationadapter-piCrossfire.AdapterPiadapter-pi
Enrollment, fleet connection and private listener hostadapter-crossfireCrossfire.AdapterCrossfireadapter-crossfire
Fleet lifecycle and pure portsfleetd/domainFleetd.Domainfleetd/domain
Fleet configuration and API contractsfleetd/contractsFleetd.Contractsfleetd/contracts
Fleet private persisted statefleetd/storageFleetd.Storagefleetd/storage
Concrete runtime/listener compositionfleetd/adapter-executionFleetd.AdapterExecutionfleetd/adapter-execution
Daemon clientfleet/daemon-clientFleet.DaemonClientfleet/daemon-client
Strict SSH transportfleet/ssh-transportFleet.SshTransportfleet/ssh-transport

Domain code depends on kernel values and declared pure ports, never concrete transport, SQLite, runtime, filesystem or UI implementations. Composition roots assemble adapters. Applications are not reusable dependencies and product dependencies cannot reference test fixtures or build tooling.

fleetd owns one shared listener host and native lifecycle. Its daemon application owns the user-scoped private API, configuration, signals and composition only. fleet uses that API even locally; SSH wraps the same private API remotely. crossfire coordinates leases, authorization, activity and receipts but never launches an agent. Native adapters encapsulate engine-specific protocols.

Knex alone creates and migrates databases. Tinqer, EF and Diesel own their respective application queries over the exact same schema. Generated entities, tables and precompiled queries remain inside the SQLite adapter. No native ORM creates a schema or silently migrates on startup. The shared one-shot migration container runs after writers stop and a required backup is verified. Filesystem-state conversions are explicit operations, not alternate runtime readers, SQL migration owners or permission to reset a database.

Required target differences#

  • C# owns its EF design-time compiler projects; Rust uses generated Diesel table metadata and typed native query expressions. Neither creates imitation packages for a compiler mechanism it does not use.
  • Native targets own process and TOML libraries because those runtimes require them. Node uses its platform process/HTTP APIs and pinned TOML dependency.
  • Rust domain orchestration uses Tokio concurrency and serde_json values as native primitives, equivalent to Promise/Task and native JSON values in the other targets. The graph names these dependencies explicitly; unknown external libraries and concrete Tokio/std filesystem, transport and process operations remain rejected. No facade hides such operations behind a purported pure port.
  • Rust owns a Linux pidfd-based readiness reactor and native process-group guard. The guard is a process-adapter executable, not another product, listener or service. It runs only with an active engine, holds an exact private lock and terminates the owned native group if fleetd exits, including SIGKILL. No terminal scraping, history rewriting, global service or unsafe first-party Rust is introduced.
  • Runtime container images select the shared pinned IANA timezone data explicitly. Rust’s standalone server also retains that data in its distribution; standalone .NET products use their installed system timezone support. Browser assets, Element, Conduit and onboarding releases are shared build inputs, not separately maintained copies.
  • Managed and NativeAOT outputs use one C# implementation. Rust uses one ahead-of-time native implementation. None falls back to another backend.

Credential refresh copies only the selected model/provider configuration and login, never unrelated MCP servers, profiles or host permission overrides. Every native activation refreshes that login; removing host login removes the stale native copy. Corrupt/public/hard-linked/symlinked private state fails closed. Private HTTP/WebSocket transports explicitly avoid ambient proxies in every implementation. Structure checks and behavioral tests run separately. Matching directory layouts do not establish that the implementations behave alike.

Architecture and verification gates#

The file audit and per-file map cover every Node/C# file, including manifests, internal tests, operator tools and framework-specific build inputs. The reproducible inventory checks explicit semantic owners without claiming that matching paths prove behavior.

scripts/verify-implementation-layout.mjs checks every product/application and 19 semantic package pairs, rejects superseded service-level source owners and keeps one UI and one common assertion bank. scripts/native-architecture.mjs checks the Cargo/.NET graphs, pure-domain boundaries, build/dev edges and application reach-through. Negative fixtures prove that violations are rejected.

npm run verify:parity -- /absolute/new-evidence-directory builds and exercises Node, managed C#, NativeAOT C# and Rust. The same installed-product assertions run against all selected products; native unit/adapter tests remain language-owned. Container, same-file SQLite and Knex migration tests run as additional checks.