Workspace Shape And Orchestration¶
A Flutter product of any size stops being one package quickly: a user app, an admin app, and a shared package holding the models both of them and the server agree on. The risk is not that the split is wrong; it is that the split silently stops being tested together.
R-WORKSPACE-1 - The workspace declares its packages and has reproducible bootstrap, analyze, and test commands¶
Severity. high
Rule. Every Flutter/Dart package in the repo is declared in one workspace manifest -
melos.yaml, or a workspace-root pubspec.yaml carrying a melos: or workspace: key -
and a single documented command bootstraps, analyzes, and tests all of them.
Why. An undeclared package is a package CI silently stops testing. The failure mode is not a red build; it is a green one that never ran.
Evidence.
- Source/config:
melos.yamlor the workspace-rootpubspec.yaml, and the package list it resolves to. - CI/artifacts: a job that runs bootstrap, then analyze, then test.
- Runtime/deploy: the CI log naming every package it visited, so a missing package is visible as an absence.
- Exception:
acceptedExceptionnaming the excluded package, why it is excluded, and the compensating check that covers it.
vcqa. Compare the workspace globs against every discovered pubspec.yaml location;
flag any package not covered by a glob, and any workspace whose bootstrap command is
documented only in a contributor's shell history.
References. https://melos.invertase.dev/, https://dart.dev/tools/pub/workspaces
R-WORKSPACE-2 - Static analysis is workspace-wide and fatal¶
Severity. medium
Rule. flutter analyze or dart analyze runs across every package with warnings and
infos fatal, alongside a formatting check.
Why. Dart's analyzer is the closest thing this stack has to a type gate, and a non-fatal analyzer is a linter nobody reads. Per-package analysis that stops at the first package leaves the rest unanalyzed.
Evidence.
- Source/config: an
analysis_options.yamlper package, or one inherited from the workspace root. - CI/artifacts:
melos run analyzeor equivalent, invoked with--fatal-infosor--fatal-warnings, and adart format --set-exit-if-changedgate. - Exception: a per-line
// ignore:carrying a reason, or anacceptedExceptionfor a generated-source boundary that names the generator.
vcqa. Check for --fatal-infos/--fatal-warnings and a format gate; flag analyze
steps that run in only one package directory while the workspace declares several.
References. https://dart.dev/tools/analysis, https://docs.flutter.dev/reference/flutter-cli
R-SHARED-1 - Shared-package changes prove every consumer still works¶
Severity. high
Rule. A change to a shared Dart package runs the tests of every package that depends on it, in the same CI run.
Why. A shared model is a wire contract between two independently released clients and a server. Testing the shared package alone proves the model is self-consistent, which was never the question.
Evidence.
- Source/config: the dependency edges in each consumer's
pubspec.yaml. - CI/artifacts: a job that runs the app and admin test suites, not only the shared package's own suite, on a shared-package change.
- Exception:
acceptedExceptionwhere a consumer is genuinely decoupled by a versioned release boundary, naming the version-compatibility check that replaces the consumer run.
vcqa. Read workflow paths: filters and flag any that can skip consumer tests when
only shared/** changed - a path filter is the usual way this rule is broken without
anyone deciding to break it.
References. https://dart.dev/tools/pub/dependencies
R-SHARED-2 - The client/server field contract is declared once and drift is detected¶
Severity. high
Rule. The set of fields a client may write, and the set only the server may write, is
declared in the shared package, mirrored in firestore.rules and in the Cloud Functions
source, and a test fails when the copies diverge.
Why. This one contract is enforced in three runtimes that cannot import each other: a Dart client, a rules file, and a TypeScript server. Three hand-maintained copies of a security-relevant list will drift, and the drift will be discovered in production.
Evidence.
- Source/config: all three declarations, each pointing at the others.
- CI/artifacts: a test that reads
firestore.rulesas text from Dart or TypeScript and asserts the field set matches; its failing output must name the diverging field. - Exception:
acceptedExceptionwhere a single generator emits all three from one source of truth, naming the generator and the check that the generated files are current.
vcqa. Look for a test that opens firestore.rules from outside the rules suite; a repo
with a server-owned field list and no such test fails this rule even if the three copies
currently agree.
References. https://firebase.google.com/docs/firestore/security/rules-structure