FlowX is specification-first. docs/ is normative: code that contradicts it
is either a bug in the code, or an ADR that has not been written yet
(constraint C8). That makes contributing here a little different from most
repositories, so please read this before opening a pull request.
| Kind | Start with | Requires |
|---|---|---|
| Design change — new concept, changed semantics, new extension point | an ADR pull request | discussion and acceptance before any code |
| Implementation — build something already specified | the issue for that spec section | tests first, then code, then doc updates |
| Documentation — clarify, correct, add examples | a pull request | consistency with the specification |
If your change alters what FlowX is, it is a design change. Open the ADR first; a rejected ADR costs an hour, a rejected implementation costs a week.
A pull request is complete when all of these hold:
- Tests written before the implementation (red → green → refactor), and the pull request description says which behaviour they pin down
- Architecture fitness functions pass (
tests/FlowX.Architecture.Tests) - Benchmarks pass their budgets — zero allocations where the budget says zero, and no p95 over a documented ceiling (14-Performance)
-
flowx diffis clean, or a major version bump plus an ADR is included - The relevant
docs/section is updated in the same pull request - An ADR is included for every significant decision, with a "Revisit when" clause
- Every new diagnostic has a title, a message naming the symbol, a suggested fix and a help URI
- Public API changes respect SemVer and the 2-minor deprecation window
-
PublishAot=truestill succeeds with no trim warnings - Commits are signed off (
git commit -s) — DCO
Two notes on that list. "No regression > 5 %" was removed because nothing
enforces it: scripts/check-benchmark-budgets.py treats absolute and ratio
drift as advisory and blocks only on allocation changes and a p95 over a
documented ceiling. WP-3's assumption that ratios are machine-independent was
withdrawn after two runs of the same commit disagreed by 63 % on ratio and 159 %
on absolute time; drift becomes a gate (--strict) once the baseline is recorded
on dedicated hardware. And the DCO line is a convention, not a check — no
workflow verifies Signed-off-by. Both are asked for in review.
These follow from 03-Design-Principles. The third column says whether a mechanism actually enforces the row today, because a rule printed next to a gate reads as a gate:
| Rule | Enforced by | Runs today |
|---|---|---|
| Cognitive complexity ≤ 15 per method | S3776 |
no — set to none, see below |
| Line ≥ 80 % / branch ≥ 75 % coverage | Coverage thresholds job, quality.yml |
yes, but whole assembly, not new code |
| Zero blocker Sonar issues | Sonar default profile + TreatWarningsAsErrors |
yes, on every compile |
| Zero critical Sonar issues | Sonar quality gate job, quality.yml |
no — needs SONAR_TOKEN |
No reflection in FlowX.Runtime |
NoReflectionOnHotPath |
yes |
No mutable statics in FlowX.Runtime |
RuntimeHasNoMutableStatics |
yes |
| No cyclic dependencies | NoCyclicDependencies |
yes |
FlowX.Abstractions has zero package references |
AbstractionsHasNoDependencies |
yes |
| Dependencies point inward | LayersPointInward |
yes |
| Structured logging only — data as fields | review | no analyzer — nothing logs yet, see below |
| No swallowed exceptions, no magic numbers, no boolean behaviour switches | review | — |
| Names describe intent, not mechanism | 04 §11 | review |
Four rows of this table were wrong, and each is corrected rather than removed: they are the bar, and three of the four are one edit or one secret away from being enforced.
- "Cognitive complexity ≤ 15 per method — SonarAnalyzer, CI gate" was false.
SonarAnalyzer.CSharpis referenced and its default profile does gate the build, butS3776shipsIsEnabledByDefault=false, and.editorconfigsets it tononeexplicitly. Enabled, it reports 12 methods over the threshold, worst 47 (FlowEngine.RunRangeAsync) — part of the 54 findings across the four complexity and size rules listed in 21 §2.6. The threshold is pinned inSonarLint.xml, so turning the row on once that debt is paid is a one-word edit. Until then it is a review instruction. - "≥ 80 % coverage on new code" overstated the scope. The Coverlet gate that runs measures the whole assembly; the diff-scoped half is Sonar's and does not run.
- "Zero blocker/critical Sonar issues" merged a gate that runs with one that
does not. Blocker is real, on every compile. Critical belongs to the Sonar
quality gate job, which exits 0 when
SONAR_TOKENis absent — and the token is not configured. The job emits a warning annotation naming the rows it did not evaluate, so its green tick is not mistakeable for a pass. - "Structured logging only — analyzer + review" named an analyzer that is not
there. Nothing under
src/constructs anILogger,ActivitySourceorMeterat all (12-Observability, P5), so this is a commitment about code that has not been written.CA1848is on as a warning and will apply the day something logs.
Folder-by-feature inside layers. Tests mirror source 1:1. One composition root. Namespace equals folder path.
// 1 — RED: state the behaviour, watch it fail for the right reason
[Fact]
public async Task Retry_does_not_exceed_the_flow_deadline()
{
var host = FlowTestHost.For<SlowFlow>().WithVirtualTime()
.WithDeadline(TimeSpan.FromSeconds(5)).Build();
var outcome = await host.RunAsync(AnInput());
outcome.Should().HaveFailedWith("flow.deadline_exceeded");
host.Trace.RetryAttempts.Should().BeLessThan(3); // the policy stopped early
}
// 2 — GREEN: the smallest change that passes
// 3 — REFACTOR: with the test greenResilience and timing behaviour must be tested with WithVirtualTime(). A test
that sleeps in real time will be asked to change.
Important
FlowTestHost exists now, but the block above is not quite what it ships.
FlowX.Testing runs a real flow through a real engine with capabilities
substituted by capability id — Substitute("payment.capture", …) — because
that is what the plan and the manifest are keyed by. FlowTestHost.For<TFlow>()
is not offered: the generated dispatcher takes concrete sealed capability types,
so finding it would need reflection over generated members, which constraint C2
forbids, and a container to construct them, which is the mock framework the
roadmap excludes. Naming the plan and the dispatcher costs one line.
host.Trace is real. WithVirtualTime() is not, and cannot be until a
policy executes — the retry this example asserts on still cannot happen, because
policy chains reach the plan and the runtime never reads them. Use
WithClock(...) for deadlines. See
23-Testing-Strategy for the shipped surface and
for what is deliberately absent.
Any pull request touching FlowX.Runtime or FlowX.Compiler must include
benchmark output before and after:
./scripts/run-benchmarks.sh # run everything, then gate
./scripts/run-benchmarks.sh '*Dispatch*' # a subset — runs, does not gateThis said flowx bench --compare origin/main. The CLI has no bench verb —
it has graph, manifest, diff and verify --cost (22-CLI) — and there is
no --compare. The script above is what runs the harness and gates it against
docs/benchmarks/baseline.json.
A regression needs either a fix or an ADR explaining the trade that was deliberately accepted. "It's only 3 %" is not an argument in a platform whose overhead is multiplied across an entire estate — but note that a 3 % drift is not something CI currently fails on, for the reason given under Definition of Done. Bringing it is the author's job, not the gate's.
Third-party plugins live in their own repositories. To be listed as compatible:
- depend on
FlowX.Abstractionsonly - pass the full
FlowX.Conformance.Testssuite for every extension point - publish the conformance result, an SBOM, a licence and a sample
- follow the checklist in 17 §9
FlowX.Conformance.Tests does not exist, so the second bullet is a
requirement nobody can meet yet. It is a P3 deliverable
(09 §8, ADR-0009),
and the fitness function that would check it — PluginsPassConformance — is
recorded as blocked rather than overlooked in
21 §2.4.
The bullet stays because it is the bar; it is not a bar that can be cleared
today.
Do not open a public issue. See SECURITY.md for the private disclosure
process. Response SLA: critical 48 hours, high 7 days.
Reviewers are asked to check, in this order:
- Does it match the specification? If not, where is the ADR?
- Were the tests written first, and do they pin down behaviour rather than implementation?
- Does it hold the budgets?
- Would a mid-level engineer maintain this in six months?
- Is the failure path designed, not just the happy path?
A pull request that is correct but undocumented is not done. A pull request that is documented but untested is not done either.