M4-01: Workspace and architecture test of the apps¶
Status: ready | Depends on: M1-01 | Milestone: M4
Goal¶
After this ticket the repository has one Dart pub workspace (15 3, R-CL-02) with every package and both apps that chapter 15 names: packages/design_system, l10n, domain, api_client, auth, device_key, local_store, sync_engine, admin_ui, bothy_viz, bothy_noise, terminal_ui, and apps/client and apps/console, each a buildable member with its manifest, analysis options, README and a first test, so that every later M4 to M6 ticket adds code to an existing member instead of inventing layout. An architecture test, tools/arch-check, holds the dependency rules of 15 3.1 for every member on every run of the gate: a forbidden import (for example package:flutter in domain, or sync_engine in apps/console) fails make arch. Documentation in code becomes a gate for Dart (23 "Documentation in code"): the lint public_member_api_docs is active in every member (generated code excluded), a public symbol without a doc comment fails make lint, dart doc runs per package without a warning, and make docs puts the result into the documentation site. The root Makefile and CI handle the workspace (one pubspec.lock, generated Dart code excluded from coverage and doc lint), and two CI jobs on macos-15 and windows-2025 run the platform tests that the later tickets add (make apps-platform). Why: rule 2 of the owner ("strictly modular, dependencies only through defined interfaces") needs a mechanical check before the first line of client code, and the later tickets of M4, M5 and M6 run in parallel on these members.
Read first¶
docs/PLAN.md: section 3 "Way of working per ticket"; section 5 "Test strategy" (rows "Unit", "Golden", "End to end"); section 10 "M4 Desktop clients" (Goal, Scope, Exit criteria item 1, Test strategy, Risks, the ticket table: this row, M4-02, M4-04, M4-08, M4-10, M4-18, M4-35, which build on the members created here); section 11 rows M5-01 and M5-04 (native code indevice_keyandapps/client); section 12 rows M6-01 and M6-10 (console andbothy_viz); section 19 P-01, P-09, P-10, P-11, P-16.docs/spec/15-client-architecture.md: 1 "Principles", 2 "Platforms", 3 "Workspace", 3.1 "Dependency rules" (the diagram and rules 1 to 7), 4 "State" (Riverpod 3), 6 (drift, thehooksexcerpt), 20 "Test strategy", 21 (R-CL-01, R-CL-02, R-CL-13, R-CL-14), 23.1 "Packages and ports".docs/spec/23-quality.md: 1 "Principles" (no silent skip, R-QA-02), 2 "Gates", 3 "Component contract" (row "Dart package") and "Documentation in code" (row "Dart"), 4 "CI" (runnersmacos-15andwindows-2025for the app builds), 10 (R-QA-02, R-QA-14), 11 (quality.coverage.dart_min_percent).docs/spec/02-architecture.md: 6 "Code layout" (blocksapps/andpackages/, "Places that carry the product name").- ADRs:
0027-flutter-for-all-apps.md("Confirmation": the architecture test),0028(web only for the console),0029-client-state-local-database-and-offline-queue.md("Confirmation": widgets read only through providers),0038-local-gates-first-one-command-identical-to-ci.md,0039-only-github-hosted-runners-public-from-the-first-push.md,0045(documentation site). docs/DECISIONS.md: D-W-54 (MkDocs and Zensical), D-W-94 (English), D-W-100, D-W-114; section 10 for the format of new rows.docs/quality/gates.md,docs/quality/toolchain.md(rowsubosito/flutter-action),docs/quality/licenses.md,docs/quality/red-team-gate.md,docs/quality/push-discipline.md,CONTRIBUTING.md("Development setup").- Briefs:
docs/briefs/M1-01-server-gates.md(the gate pattern andtools/ci/tests/test_make_server_gates.py);docs/briefs/M2-38-noise-core.md(Design note 3: the Dart lines oftestandcoverage, the CI toolchain steps; Design note 8: the Flutter pin and the workspace takeover ofpackages/bothy_noise);docs/briefs/M2-23-generated-clients.md(Design note 8: generated code and gate scope). - Code on the base branch: root
Makefile(DART_PKGS,DART_ON,fmt,fmt-check,lint,typecheck,test,coverage,arch,docs,licenses,osv,sonar);tools/ci/components.pyand its tests;.github/workflows/ci.yml(jobsplan,gates,sonar,ci-ok),.github/workflows/docs.yml;tools/ci/check_runners.py;sonar-project.properties;mkdocs.yml;apps/README.md,apps/client/README.md,apps/console/README.md,packages/README.md;packages/bothy_noise/if M2-38 has created it.
This ticket touches no API operation, no table of chapter 03, no action, no event and no configuration key.
Fixed requirements that apply¶
Owner's fixed build rules (apply verbatim):
- No MVP. New from scratch and directly in the final state; stages are only the order.
- Strictly modular: clear module and service boundaries, dependencies only through defined interfaces, no cross access.
- Documentation in code: every public module, class, function and interface with a docstring/doc comment (purpose, parameters, return value, errors, example where useful); comments explain the why, not the what. API documentation is generated from code (OpenAPI from the routes, dartdoc, Python API docs in MkDocs).
- Documentation in the repo: README per module/service, ADR for every architecture decision, CHANGELOG per release, keep the documentation site current. A change without matching documentation is not done.
- Quality gates (SonarQube, linters, type checking, security scanners, test coverage) block the merge.
Rules for every brief:
- Test first: write the failing test, run it and keep the red output for the report, then make it green, then clean up.
- No en dash and no em dash in any file you write; the
text-rulespre-commit hook rejects them, and the ARB test of M4-06 rejects them in interface texts. Use a hyphen or rephrase. - White label: examples and test data use
example.comand the people Alex, Sam and Robin; no company, host or personal names, no paths under a home directory. Interface texts come frompackages/l10n, never as literals in widgets. - Limits, windows and thresholds only through named configuration keys listed in
docs/operations/configuration.md; this ticket adds none. The coverage threshold staysquality.coverage.dart_min_percent(COVERAGE_DART_MIN). - Specification first: a change to behaviour, API, event, right or configuration updates
docs/spec/ordocs/api/in the same change. The core contract is not edited by this ticket.
Product requirements implemented here:
- One pub workspace with one resolution and one
pubspec.lockat the root; no Melos; tasks run through the make targets (15 3, R-CL-02). - The members and their kind (15 3): Flutter packages
design_system,l10n,device_key(federated plugin, Pigeon),admin_ui,bothy_viz,terminal_ui; pure Dart packagesdomain,api_client,auth,local_store,sync_engine,bothy_noise; Flutter appsapps/client(macOS, Windows, later iOS and Android) andapps/console(web). - Dependency rules 1 to 7 of 15 3.1 and its diagram, checked on every run; in addition: a member depends on another member only if the diagram reaches that member from it (directly or through other members), and only
device_keyandapps/client/{ios,macos,android,windows}contain platform code (rule 4). - Documentation in code (23):
public_member_api_docsin every member, generated code excluded;dart docper package without a warning; the result in the documentation site. - No member is published (
publish_to: none); every member has tests undertest/(23 3), and the gates fail for a member without tests or without coverage report (M2-38 Design note 3 makes this change too; if the Makefile on the base branch still skips such a member, this ticket makes both cases fail, Design note 4).
Scope¶
Create:
- Root
pubspec.yaml(workspace root, Design note 1) and the rootpubspec.lock; rootanalysis_options.yamlwith the shared rules that every member includes (Design note 3). - For each of the twelve packages under
packages/<name>/:pubspec.yaml,analysis_options.yaml,README.md(English: purpose, status "skeleton (M4-01)", allowed dependencies from 15 3.1, the ticket that fills it),lib/<name>.dart(library doc comment, no other public symbol),test/<name>_test.dart(one test that imports the library). Forbothy_noise: only if M2-38 has not created it yet; otherwise take the existing package into the workspace (Design note 1). apps/client/andapps/console/:pubspec.yaml,analysis_options.yaml,lib/main.dart(an app that shows an emptyMaterialAppwithout text, doc comment onmain),test/app_test.dart(pumps the app). No platform folder: M4-18 runsflutter createfor macOS and Windows, M5 for iOS and Android, M6-01 for web.tools/arch-check/:arch_check.py,rules.yaml,README.md(English) andtests/test_arch_check.py(Design note 2).tools/docs/dart_api_docs.pyandtools/docs/tests/test_dart_api_docs.py; the site pagedocs/reference/dart-api.md(Design note 5).tools/ci/apps_platform.pyandtools/ci/tests/test_apps_platform.py(Design note 6).tools/ci/tests/test_make_dart_workspace.py(Tests).tools/ci/lcov_filter.pyandtools/ci/tests/test_lcov_filter.py(Design note 4).tools/ci/tests/test_ci_toolchains.pyif M2-38 has not created it (Tests).
Change:
- Root
Makefile:archgains the Dart architecture test;fmt-check,lint,test,coveragefollow the workspace and exclude generated Dart code from coverage; new targetapps-platform(not part ofgates);docscallsdart_api_docs.py(Design notes 2 to 6). .github/workflows/ci.yml: Flutter from the rootpubspec.yamlin the jobsgatesandsonar; new jobsapps-macosandapps-windows(Design note 6); theci-okjob keeps having noneeds(the comment inci.ymlsays why)..github/workflows/docs.yml: Flutter formake docs.tools/ci/ci_ok.pyandtools/ci/tests/test_ci_ok.py: one rule each forapps-macosandapps-windowsthat acceptssuccessandskipped(Design note 6).tools/pyproject.toml:arch-checkin the pytesttestpathsandpythonpath, the coveragesourceand the mypyfiles, so the architecture test's own tests, coverage and type checks run in the gates; rootMakefilelint:tools/arch-checkin thebanditlist.tools/ci/components.pyand its tests: the rootpubspec.yaml,pubspec.lock,analysis_options.yamlandtools/arch-check/selectapps(Design note 7).sonar-project.properties: Dart sources and lcov paths of the members, generated Dart files excluded (Design note 4).mkdocs.yml(navigation entry fordocs/reference/dart-api.md),.gitignore(the generated API pages and.dart_tool/,build/).docs/spec/15-client-architecture.md3 and 3.1 (the extra rules of requirement 13, the workspace root),docs/spec/23-quality.md3 (row "Dart package": workspace member, generated code),docs/quality/gates.md(rowsarch,lint,docs, new rowapps-platform),docs/quality/toolchain.md(Flutter pin source),CONTRIBUTING.md("Development setup": Flutter from the rootpubspec.yaml).apps/README.md,apps/client/README.md,apps/console/README.md,packages/README.md(English: status and layout).docs/DECISIONS.mdsection 10: rowsD-M4-01-<n>; rootCHANGELOG.mdunder[Unreleased].
Out of scope¶
- Code inside the members beyond the skeleton:
api_clientgeneration (M4-02), tokens and theme (M4-04), localization (M4-06), domain entities (M4-08), login (M4-09), device keys (M4-10, M4-11), local database (M4-12), sync engine (M4-13 to M4-16), app shell and platform folders (M4-18),admin_uiscreens (M4-25 onward),terminal_ui(M4-35),bothy_viz(M6-10 onward), console (M6-01). - The lint rule "no direct API call from widgets" of ADR 0029 "Confirmation": M4-18 (the first widgets with data).
- Building and signing release artifacts (
release-build,release-apple,release-windows): M7. - Changes to the core contract.
Boundaries¶
- Work in a dedicated git worktree on the branch
feat/M4-01-workspace-arch; one writer per worktree. - Conventional Commits with the package or component as scope (for example
feat(client): ...,feat(sync-engine): ...,build(gates): ...,docs(spec): ...) and a DCO sign-off on every commit (git commit -s); squash into meaningful commits before the merge (docs/quality/push-discipline.md). - Never push to
main, never tag and never create remote repositories; the only remote steps are the branch push and the pull request ofAGENTS.md. Never publish a Dart package (publish_to: nonein everypubspec.yaml). - Do not modify files outside the scope above, except the CHANGELOG, README and
docs/DECISIONS.mdentries named in this brief. - Follow the host rules supplied by the launcher (ports, machine rules).
- Tests bind only
127.0.0.1with port 0 and make no network call; fakes replace the server unless a section below names a container. Temporary directories only viamktemp -d(DartDirectory.systemTemp.createTemp, pytesttmp_path), removed at the end. Stop only processes you started, by PID. - Never read or print secrets. No network calls except package downloads for pinned tools and pub packages and what the gates of
make checkdo themselves. - The Flutter SDK at the version pinned in the root
pubspec.yamlmust be onPATH(and Go and Docker where a section names them); without them the gates fail by design (R-QA-02), and the ticket stops withVerdict: BLOCKED <tool> missinginstead of skipping. - Never lower a threshold, disable a gate, delete a test, raise
repo.max_file_kbor extendquality/licenses/allowed.txt; never exclude hand-written code from a lint, format, coverage or doc gate. - The product name appears only at the places of 02 "Code layout" ("Places that carry the product name"); the app shows
branding.product_namefrom the instance, never a literal. - No permission logic in the client (15 1 principle 1): controls follow server responses, snapshots and events, never an own derivation from grants.
- If the core contract (chapters 00, 03, 04, 05) or
docs/api/lacks something this ticket needs, stop withVerdict: BLOCKED <reason>instead of building a deviation (P-11). - This brief was written before milestone M1 closed (D-W-100, extended to M4 by D-W-114). Where the merged code of an earlier ticket names a package, type, function, file, provider or make target differently from this brief, follow the code and list each difference in the report; where it behaves differently from the specification, stop with
Verdict: BLOCKED <reason>.
Design notes¶
-
Workspace root and members. Root
pubspec.yaml:name: bothy_workspace,publish_to: none,environmentwithsdkas a caret range of the Dart version of the pinned Flutter release andflutterwith the exact stable Flutter versionflutter --versionprints on the build host (at least the release 15 2 names),workspace:listing all fourteen members in the order of 15 3. Every member hasresolution: workspace,publish_to: none,version: 0.0.0, the sameenvironment, and only the dependencies the skeleton needs (flutterandflutter_testfor Flutter members,testandlintsorflutter_lintsas dev dependencies). Onepubspec.lockat the root, tracked; no member tracks its own lock file. Ruling for docs/DECISIONS.md: the Flutter pin lives in the rootpubspec.yaml, CI reads it there (flutter-version-file: pubspec.yaml), and every member repeats the same exactflutterline, whichtest_flutter_pins_agree(M2-38) or the test of this ticket holds equal. Ifpackages/bothy_noiseexists (M2-38 merged): add it to the workspace, setresolution: workspace, delete itspubspec.lock, keep its code and tests unchanged, and move the CI Flutter pin from that package to the root file (adapttools/ci/tests/test_ci_toolchains.pyto the new source). If it does not exist: create the skeleton; M2-38 then joins the workspace by its own Design note 8. -
Architecture test
tools/arch-check. Python standard library plus PyYAML from the pinnedtools/environment, run asuv run --project tools --locked --quiet python tools/arch-check/arch_check.py.rules.yamlis the single source of the rules: per member its kind (dart,flutter,plugin,app), its allowed member dependencies (every member that the diagram of 15 3.1 reaches from it, that is the edges and their transitive closure:apps/clientmay importdomainbecause it reaches it throughsync_engine, whileauthmay not importlocal_storebecause no path leads there), forbidden imports (package:flutter/anddart:uifor rule 1 and 7 members; forbothy_noisealsodart:ioinlib/only, because its tests read vector files withdart:io(M2-38), and any package exceptcryptographywith crypto code), and platform folders allowed (rule 4). Checks, each with a message that names the file, the line and the rule number of 15 3.1:- every
importandexportdirective inlib/,test/,integration_test/andbin/of a member (parsed line by line with a regular expression that understandsimport '...',export "...", conditional importsif (dart.library.io)and multi-line directives) that names apackage:<member>/that the member does not reach,package:flutterwhere forbidden, or any../path that leaves the member (rule 2); - every
dependenciesanddev_dependenciesentry of a memberpubspec.yamlthat names another member that it does not reach (adev_dependencyon a member for tests follows the same reach); - rule 3 (
apps/consolewithoutsync_engineandlocal_store, directly or through a member that depends on them), rule 6 (bothy_vizwithoutsync_engineandlocal_store), rule 7 (terminal_uiwithout them;bothy_noisewithout Flutter and withcryptographyas its only runtime dependency); - rule 4: a folder
ios,macos,android,windows,linuxorwebwith native sources (*.swift,*.m,*.mm,*.kt,*.java,*.cpp,*.cc,*.h) only inpackages/device_keyandapps/client;apps/console/webholds no native source; - every tracked
pubspec.yamlunderapps/andpackages/is a member listed inrules.yamland in the rootworkspace:list, and vice versa (a new package without rules fails). - Exit 0 with
arch-check: <n> members, <m> files, ok, exit 1 with one line per finding.make archruns it after the Go part when Dart members are tracked (DART_ON), otherwise printsSKIP arch-dart ...with theskipmacro; the Go part stays unchanged. Ruling for docs/DECISIONS.md: the reach of the diagram is an allow list, because a deny list would let every new edge in silently; a new edge needs a change of 15 3.1 andrules.yamlin the same change.
- every
-
Analysis options. Root
analysis_options.yaml:include: package:flutter_lints/flutter.yamlis not usable for pure Dart members, so the root file holds only theanalyzerandlintersections that all share:strict-casts,strict-inference,strict-raw-types,errors: {public_member_api_docs: error},exclude: ['**/*.g.dart', '**/*.freezed.dart', '**/*.drift.dart', '**/lib/src/generated/**', 'build/**'], and the rulespublic_member_api_docs,always_declare_return_types,avoid_print,prefer_final_locals,unawaited_futures,directives_ordering. Each member includespackage:lints/recommended.yaml(pure Dart) orpackage:flutter_lints/flutter.yaml(Flutter) and then the root rules by a relativeinclude(Dart 3.6 allows a list underinclude; if the pinned SDK does not, copy the shared rules into each member and add a test that holds the copies equal). Ruling for docs/DECISIONS.md: generated code is excluded from the doc lint, the style lint and coverage, and stays in the compile check andflutter analyzeerrors (as M2-23 Design note 8 does for the other languages). -
Root Makefile.
DART_PKGSkeeps discovering members by theirpubspec.yaml(the rootpubspec.yamlis not a member and is excluded explicitly).fmt-checkformats only hand-written files: rundart format --output=none --set-exit-if-changedover the tracked*.dartfiles of a member minus the generated patterns of note 3 (generators format their output as part of their own generation, M4-02, M4-12).lintrunsflutter analyze --fatal-infosonce at the root (the workspace analyzes all members in one run) anddart run custom_lintper member that declares it.testrunsflutter test --coverageper member, and a member with code and withouttest/fails;coveragefails for a member withoutcoverage/lcov.info. M2-38 Design note 3 makes the same two changes; if the Makefile on the base branch still skips such a member silently (if [ -d test ],|| continue), change both lines here and test them.coverageremoves the records of generated files (SF:paths matching the patterns of note 3) from eachcoverage/lcov.infobefore summing, with a small scripttools/ci/lcov_filter.py(standard library, tested), then appliesCOVERAGE_DART_MIN; a member withoutcoverage/lcov.infofails (M2-38).osvandlicensesread the rootpubspec.lockonce instead of per member.sonar-project.properties:sonar.sourcesandsonar.testsgainapps,packageswith the test globs,sonar.dart.lcov.reportPathslists the lcov file of every member, andsonar.exclusionsgains the generated patterns. Run every changed target once withCOMPONENTS=appsand once withCOMPONENTS=server(Dart lines print their SKIP lines) and fix what fails. -
API documentation in the site.
tools/docs/dart_api_docs.py --out docs/api/dart: for each member that is a package (not the apps), rundart doc --output <out>/<member>in the member; fail on a non-zero exit or on any line of the output that containswarning:orerror:; write nothing outside--out.make docsruns it beforemkdocs build --strict(withDART_ON; otherwiseSKIP dart-api-docs ...), and the generated folderdocs/api/dart/is in.gitignore, so MkDocs copies the HTML as static files into the site.docs/reference/dart-api.md(tracked, English) lists every package with a link to../api/dart/<member>/index.htmland one sentence on its purpose from 15 3;test_dart_api_docs.pyholds the list equal to the members intools/arch-check/rules.yaml..github/workflows/docs.ymlinstalls Flutter from the rootpubspec.yaml. Ruling for docs/DECISIONS.md: the HTML is generated per build and never tracked, so it cannot drift from the code. Verify thatmake linkcheck(tracked Markdown) andmkdocs build --strictstay green without loosening any validation setting; if MkDocs reports the links into the generated folder as unresolved, generate the folder before the build in bothmake docsand the Pages job and keep the strict setting. -
Platform tests and CI jobs. The pure gates run on
ubuntu-24.04; native code and desktop integration tests need macOS and Windows (ADR 0039 namesmacos-15andwindows-2025for app builds).tools/ci/apps_platform.py(standard library): readsplatform-tests.jsonof every member that has one ({"macos": [["flutter", "test", "integration_test", "-d", "macos"]], "windows": [...]}, argument lists without a shell), runs the lists for the host system (sys.platformdarwinismacos,win32iswindows) in the member folder in order and stops at the first failure; printsSKIP apps-platform <member> <system>: other hostfor the other systems andSKIP apps-platform: no member with platform testswhen none exists. No member has the file after this ticket; M4-10 is the first. Root targetapps-platformcalls the script; it is not part ofgatesbecause it needs a desktop session..github/workflows/ci.yml: jobsapps-macos(runs-on: macos-15) andapps-windows(runs-on: windows-2025), each withneeds: plan,if:theappscomponent is selected, checkout,subosito/flutter-actionwithflutter-version-file: pubspec.yamland cache,astral-sh/setup-uv, thenuv run --project tools --locked --quiet python tools/ci/apps_platform.pywithshell: bash;timeout-minutes,permissions: contents: read, actions pinned by SHA as every other job; theci-okjob gets noneeds(its comment inci.ymlexplains why:tools/ci/ci_ok.pywaits for the jobs of the run through the API); insteadRULESintools/ci/ci_ok.pygainsRule(("apps-macos",), frozenset({"success", "skipped"}))and the same forapps-windows, with cases intools/ci/tests/test_ci_ok.py.make runners-checkandmake lint(actionlint, zizmor) stay green. Ruling for docs/DECISIONS.md. -
Component selection.
GATE_COMPONENTS["apps"]already ownsapps/,packages/anddesign/; add the root filespubspec.yaml,pubspec.lock,analysis_options.yamland the prefixtools/arch-check/(a change there must run the Dart gates); keep every existing case oftest_components.pygreen and add one per new entry. -
Skeleton content. Each
lib/<name>.dartstarts with the SPDX header and alibrary;directive carrying the doc comment (purpose from 15 3, "Skeleton created by M4-01; filled by"). Each first test asserts something real about the member that later tickets keep true (for example domain: the library imports without Flutter; a Flutter member: a widget test pumps an emptySizedBoxfrom the package's test helper). The two apps show no text, so no l10n is needed yet.device_keyis created as a federated plugin skeleton withflutter create --template=plugin --platforms=macos,windowsreduced to the Dart interface package without native code (M4-10 adds the platform implementations); if the template produces native files, delete them so that rule 4 and the no-code state agree. -
Rulings for docs/DECISIONS.md, one row each with date, decision, reason and place (
D-M4-01-<n>): Flutter pin in the rootpubspec.yaml(note 1); thebothy_noisetakeover (note 1); the reach of the diagram as an allow list (note 2); generated code out of doc lint, style lint and coverage (note 3); one rootflutter analyze(note 4); API documentation generated per build (note 5); platform tests outsidegatesin two CI jobs (note 6); component selection of the root files (note 7).
Tests (write them first)¶
Repository tools (pytest, tmp_path, run from tools/):
arch-check/tests/test_arch_check.py, each on a throwaway tree with fake members written by the test:test_clean_workspace_passes(members along the edges of the diagram, including a transitive import such asapps/clientimportingdomain);test_flutter_import_in_domain_fails(import 'package:flutter/widgets.dart';inpackages/domain/lib/src/x.dartgives a finding naming rule 1, file and line);test_dart_ui_import_in_sync_engine_fails;test_console_depends_on_sync_engine_fails(inpubspec.yaml, rule 3) andtest_console_imports_local_store_through_admin_ui_fails(admin_uigets a dependency onlocal_store: rule 3 by transitivity and the missing edge);test_dependency_not_reached_fails(authimportslocal_store);test_relative_import_leaving_member_fails(import '../../domain/lib/x.dart');test_conditional_and_multiline_imports_are_read;test_native_source_outside_allowed_places_fails(packages/design_system/macos/Classes/x.swift) andtest_native_source_in_device_key_passes;test_bothy_noise_extra_dependency_fails(a second runtime dependency),test_bothy_noise_dart_io_in_lib_failsandtest_bothy_noise_dart_io_in_test_passes;test_unlisted_member_failsandtest_listed_member_missing_fails;test_rules_cover_every_member_of_the_repository(the realrules.yamlagainst the real tree);test_real_repository_passes(the real tree, exit 0).
docs/tests/test_dart_api_docs.py:test_warning_line_failsandtest_nonzero_exit_fails(a fakedartonPATHthat printswarning: ...or exits 1);test_writes_only_below_out;test_page_lists_every_package(the realdocs/reference/dart-api.mdagainstrules.yaml).ci/tests/test_apps_platform.py:test_runs_commands_for_host_system(fakeflutteronPATHrecords its arguments),test_prints_skip_for_other_system,test_stops_at_first_failure,test_no_member_prints_skip,test_rejects_string_command(a command that is a string instead of a list fails the run).ci/tests/test_lcov_filter.py: generated records dropped by every pattern of note 3, hand-written records kept, the sum ofLFandLHrecomputed.ci/tests/test_make_dart_workspace.py(a temporary git repository with a copy of the rootMakefile, fakeflutter,dartanduvfirst onPATH,MAKEFLAGSandMAKELEVELdropped, the pattern oftest_make_server_gates.py):test_arch_runs_dart_check_when_members_exist;test_arch_skips_dart_without_members;test_lint_runs_one_root_analyze;test_coverage_ignores_generated_files(an lcov with a fully uncovered*.g.dartand a covered hand file passes);test_member_without_tests_fails;test_docs_runs_dart_api_docs;test_apps_platform_not_in_gates.ci/tests/test_ci_toolchains.py(extend if M2-38 created it, otherwise create):test_flutter_version_file_is_root_pubspec(jobsgates,sonar,apps-macos,apps-windows, and the docs workflow);test_flutter_pins_agree(root and every member: the same exactenvironment.flutter);test_ci_ok_has_no_needs.ci/tests/test_ci_ok.py:apps-macosandapps-windowsfailing makeci-okfail;successorskippedpasses.ci/tests/test_components.py: new cases for the rootpubspec.yaml,pubspec.lock,analysis_options.yamlandtools/arch-check/rules.yaml(each selectsapps).
Dart (in the members, run by make test):
- one test per member as in Design note 8.
- Doc lint proof, as a pytest in
ci/tests/test_make_dart_workspace.py:test_public_symbol_without_doc_fails_lintcopies one real member into a temporary workspace, addsint undocumented() => 1;to its library and asserts thatflutter analyze --fatal-infosfails namingpublic_member_api_docs; marked to run only whenflutteris onPATH(the gate host has it; the test prints why it is skipped otherwise and the report states where it ran).
Commands that must pass, from the repository root:
flutter pub getat the root (one resolution, onepubspec.lock).cd tools && uv run --locked --quiet pytest arch-check/tests ci/tests/test_apps_platform.py ci/tests/test_lcov_filter.py ci/tests/test_make_dart_workspace.py ci/tests/test_ci_toolchains.py ci/tests/test_ci_ok.py ci/tests/test_components.py docs/tests/test_dart_api_docs.py ci/tests/test_make_server_gates.pymake arch,make fmt-check,make lint,make test,make coverage,make docs,make linkcheck,make licenses,make osv,make runners-checkmake apps-platform(prints its SKIP line)make check
Acceptance criteria¶
-
flutter pub getin the root folder resolves all fourteen members with onepubspec.lock; every member haspublish_to: none,resolution: workspace, tests and a README. -
make archfails on a forbidden import (package:flutterindomain), onsync_engineorlocal_storeinapps/console(directly or transitively), on a dependency the diagram does not reach, on native code outside the allowed places and on an unlisted member; it passes on the repository. - A public symbol without a doc comment fails
make lint; generated code is excluded from doc lint, style lint and coverage and stays in the compile check. -
dart docruns per package without a warning, andmake docsputs the result into the site;docs/reference/dart-api.mdlists every package. - CI installs Flutter from the root
pubspec.yaml; the jobsapps-macosandapps-windowsruntools/ci/apps_platform.pyand are rules oftools/ci/ci_ok.py(noneedsonci-ok);make runners-checkis green. - The root files and
tools/arch-check/select theappsgates. - 15 3 and 3.1, 23 3, gates.md, toolchain.md and CONTRIBUTING.md describe the workspace, the extra rules and the gates; the rulings of Design note 9 are recorded.
-
make checkis green.
Definition of done¶
- Code and tests as specified; docstring or doc comment on every public symbol (Python with ruff
D, Dart withpublic_member_api_docs); comments explain why. - READMEs of
tools/arch-check, every member,apps,apps/client,apps/consoleandpackageswritten or updated (layout, allowed dependencies, which ticket fills the member, how to run the platform tests). docs/quality/gates.md,docs/quality/toolchain.mdandCONTRIBUTING.mdupdated; 15 and 23 changed in the same change; rulings of Design note 9 indocs/DECISIONS.mdsection 10.- Root
CHANGELOG.mdentry under 'Unreleased' (workspace, architecture test, Dart API docs, platform jobs). - An ADR only if you take an architecture decision beyond this brief (MADR 4 as in
docs/adr/0000-template-madr-4.md, headings and text in English, next free number,make adr-index). make checkgreen.- An independent code review over the diff, then the red team gate (
docs/quality/red-team-gate.md, verdictJAorNEIN); onNEINfix with a test per finding and have the delta checked again. Record both verdicts. - Land the change as
AGENTS.md("How work lands onmain") describes: a pull request from this branch, squash-merged only when review, red team andmake check,ci-okand the review threads are green; thengit worktree remove.
Report¶
Write to {{REPORT_PATH}}:
- First line exactly
Verdict: DONEorVerdict: BLOCKED <reason>. - Commits (SHA and subject), changed files with line counts, test results with counts (the red run before the change and the green run after), the summary of
make check. - Review and red team verdicts and the findings fixed.
- The pinned Flutter and Dart versions, the members created and whether
bothy_noisewas taken over or created; the CI run ofapps-macosandapps-windows(SKIP lines expected). - What is not proven (for example a platform test that ran only in CI, or a check done by hand).
- Follow-ups that could not be done in this scope, each with its reason, every difference to the names of this brief (D-W-100, D-W-114), and spec gaps found.
Then write {{DONE_PATH}} with exit=0 sha=<sha> or exit=1 <reason>.