From 723059182b217ea0facd1f7763d843b2f17ecac6 Mon Sep 17 00:00:00 2001 From: Super Genius Date: Mon, 10 Aug 2026 19:04:36 -0700 Subject: [PATCH 01/49] =?UTF-8?q?wip:=20restructure=20=E2=80=94=20extract?= =?UTF-8?q?=20Flutter=20UI=20to=20app/=20with=20scaffold=20submodule=20+?= =?UTF-8?q?=20neoswarm=5Fffi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitmodules | 3 + GNUS-NEO-SWARM | 2 +- app/src/.gitignore | 45 + app/src/.metadata | 42 + app/src/README.md | 16 + app/src/analysis_options.yaml | 28 + app/src/android/.gitignore | 14 + app/src/android/app/build.gradle.kts | 44 + .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 45 + .../gnus/flutter/flutter_app/MainActivity.kt | 5 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + app/src/android/build.gradle.kts | 21 + app/src/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 5 + app/src/android/settings.gradle.kts | 25 + app/src/ios/.gitignore | 34 + app/src/ios/Flutter/AppFrameworkInfo.plist | 26 + app/src/ios/Flutter/Debug.xcconfig | 2 + app/src/ios/Flutter/Release.xcconfig | 2 + app/src/ios/Podfile | 41 + app/src/ios/Runner.xcodeproj/project.pbxproj | 616 +++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 101 ++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + app/src/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 +++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 1418 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 + app/src/ios/Runner/Base.lproj/Main.storyboard | 26 + app/src/ios/Runner/Info.plist | 49 + app/src/ios/Runner/Runner-Bridging-Header.h | 1 + app/src/ios/RunnerTests/RunnerTests.swift | 12 + app/src/lib/main.dart | 193 ++++ app/src/linux/.gitignore | 1 + app/src/linux/CMakeLists.txt | 128 +++ app/src/linux/flutter/CMakeLists.txt | 88 ++ .../flutter/generated_plugin_registrant.cc | 23 + .../flutter/generated_plugin_registrant.h | 15 + app/src/linux/flutter/generated_plugins.cmake | 26 + app/src/linux/runner/CMakeLists.txt | 26 + app/src/linux/runner/main.cc | 6 + app/src/linux/runner/my_application.cc | 130 +++ app/src/linux/runner/my_application.h | 18 + app/src/macos/.gitignore | 7 + app/src/macos/Flutter/Flutter-Debug.xcconfig | 2 + .../macos/Flutter/Flutter-Release.xcconfig | 2 + .../Flutter/GeneratedPluginRegistrant.swift | 24 + app/src/macos/Podfile | 42 + app/src/macos/Podfile.lock | 156 ++++ .../macos/Runner.xcodeproj/project.pbxproj | 801 ++++++++++++++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 99 ++ .../contents.xcworkspacedata | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + app/src/macos/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 68 ++ .../AppIcon.appiconset/app_icon_1024.png | Bin 0 -> 102994 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 0 -> 5680 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 0 -> 520 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 0 -> 14142 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 0 -> 1066 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 0 -> 36406 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 0 -> 2218 bytes app/src/macos/Runner/Base.lproj/MainMenu.xib | 343 +++++++ app/src/macos/Runner/Configs/AppInfo.xcconfig | 14 + app/src/macos/Runner/Configs/Debug.xcconfig | 2 + app/src/macos/Runner/Configs/Release.xcconfig | 2 + .../macos/Runner/Configs/Warnings.xcconfig | 13 + .../macos/Runner/DebugProfile.entitlements | 12 + app/src/macos/Runner/Info.plist | 32 + app/src/macos/Runner/MainFlutterWindow.swift | 15 + app/src/macos/Runner/Release.entitlements | 8 + app/src/macos/RunnerTests/RunnerTests.swift | 12 + app/src/pubspec.lock | 866 ++++++++++++++++++ app/src/pubspec.yaml | 96 ++ app/src/scaffold | 1 + app/src/test/widget_test.dart | 30 + app/src/windows/.gitignore | 17 + app/src/windows/CMakeLists.txt | 108 +++ app/src/windows/flutter/CMakeLists.txt | 109 +++ .../flutter/generated_plugin_registrant.cc | 26 + .../flutter/generated_plugin_registrant.h | 15 + .../windows/flutter/generated_plugins.cmake | 28 + app/src/windows/runner/CMakeLists.txt | 40 + app/src/windows/runner/Runner.rc | 121 +++ app/src/windows/runner/flutter_window.cpp | 71 ++ app/src/windows/runner/flutter_window.h | 33 + app/src/windows/runner/main.cpp | 43 + app/src/windows/runner/resource.h | 16 + app/src/windows/runner/resources/app_icon.ico | Bin 0 -> 33772 bytes app/src/windows/runner/runner.exe.manifest | 14 + app/src/windows/runner/utils.cpp | 65 ++ app/src/windows/runner/utils.h | 19 + app/src/windows/runner/win32_window.cpp | 288 ++++++ app/src/windows/runner/win32_window.h | 102 +++ 128 files changed, 5982 insertions(+), 1 deletion(-) create mode 100644 app/src/.gitignore create mode 100644 app/src/.metadata create mode 100644 app/src/README.md create mode 100644 app/src/analysis_options.yaml create mode 100644 app/src/android/.gitignore create mode 100644 app/src/android/app/build.gradle.kts create mode 100644 app/src/android/app/src/debug/AndroidManifest.xml create mode 100644 app/src/android/app/src/main/AndroidManifest.xml create mode 100644 app/src/android/app/src/main/kotlin/ai/gnus/flutter/flutter_app/MainActivity.kt create mode 100644 app/src/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 app/src/android/app/src/main/res/drawable/launch_background.xml create mode 100644 app/src/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/android/app/src/main/res/values-night/styles.xml create mode 100644 app/src/android/app/src/main/res/values/styles.xml create mode 100644 app/src/android/app/src/profile/AndroidManifest.xml create mode 100644 app/src/android/build.gradle.kts create mode 100644 app/src/android/gradle.properties create mode 100644 app/src/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 app/src/android/settings.gradle.kts create mode 100644 app/src/ios/.gitignore create mode 100644 app/src/ios/Flutter/AppFrameworkInfo.plist create mode 100644 app/src/ios/Flutter/Debug.xcconfig create mode 100644 app/src/ios/Flutter/Release.xcconfig create mode 100644 app/src/ios/Podfile create mode 100644 app/src/ios/Runner.xcodeproj/project.pbxproj create mode 100644 app/src/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 app/src/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 app/src/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 app/src/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 app/src/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 app/src/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 app/src/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 app/src/ios/Runner/AppDelegate.swift create mode 100644 app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 app/src/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 app/src/ios/Runner/Base.lproj/Main.storyboard create mode 100644 app/src/ios/Runner/Info.plist create mode 100644 app/src/ios/Runner/Runner-Bridging-Header.h create mode 100644 app/src/ios/RunnerTests/RunnerTests.swift create mode 100644 app/src/lib/main.dart create mode 100644 app/src/linux/.gitignore create mode 100644 app/src/linux/CMakeLists.txt create mode 100644 app/src/linux/flutter/CMakeLists.txt create mode 100644 app/src/linux/flutter/generated_plugin_registrant.cc create mode 100644 app/src/linux/flutter/generated_plugin_registrant.h create mode 100644 app/src/linux/flutter/generated_plugins.cmake create mode 100644 app/src/linux/runner/CMakeLists.txt create mode 100644 app/src/linux/runner/main.cc create mode 100644 app/src/linux/runner/my_application.cc create mode 100644 app/src/linux/runner/my_application.h create mode 100644 app/src/macos/.gitignore create mode 100644 app/src/macos/Flutter/Flutter-Debug.xcconfig create mode 100644 app/src/macos/Flutter/Flutter-Release.xcconfig create mode 100644 app/src/macos/Flutter/GeneratedPluginRegistrant.swift create mode 100644 app/src/macos/Podfile create mode 100644 app/src/macos/Podfile.lock create mode 100644 app/src/macos/Runner.xcodeproj/project.pbxproj create mode 100644 app/src/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 app/src/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 app/src/macos/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 app/src/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 app/src/macos/Runner/AppDelegate.swift create mode 100644 app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png create mode 100644 app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png create mode 100644 app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png create mode 100644 app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png create mode 100644 app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png create mode 100644 app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png create mode 100644 app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png create mode 100644 app/src/macos/Runner/Base.lproj/MainMenu.xib create mode 100644 app/src/macos/Runner/Configs/AppInfo.xcconfig create mode 100644 app/src/macos/Runner/Configs/Debug.xcconfig create mode 100644 app/src/macos/Runner/Configs/Release.xcconfig create mode 100644 app/src/macos/Runner/Configs/Warnings.xcconfig create mode 100644 app/src/macos/Runner/DebugProfile.entitlements create mode 100644 app/src/macos/Runner/Info.plist create mode 100644 app/src/macos/Runner/MainFlutterWindow.swift create mode 100644 app/src/macos/Runner/Release.entitlements create mode 100644 app/src/macos/RunnerTests/RunnerTests.swift create mode 100644 app/src/pubspec.lock create mode 100644 app/src/pubspec.yaml create mode 160000 app/src/scaffold create mode 100644 app/src/test/widget_test.dart create mode 100644 app/src/windows/.gitignore create mode 100644 app/src/windows/CMakeLists.txt create mode 100644 app/src/windows/flutter/CMakeLists.txt create mode 100644 app/src/windows/flutter/generated_plugin_registrant.cc create mode 100644 app/src/windows/flutter/generated_plugin_registrant.h create mode 100644 app/src/windows/flutter/generated_plugins.cmake create mode 100644 app/src/windows/runner/CMakeLists.txt create mode 100644 app/src/windows/runner/Runner.rc create mode 100644 app/src/windows/runner/flutter_window.cpp create mode 100644 app/src/windows/runner/flutter_window.h create mode 100644 app/src/windows/runner/main.cpp create mode 100644 app/src/windows/runner/resource.h create mode 100644 app/src/windows/runner/resources/app_icon.ico create mode 100644 app/src/windows/runner/runner.exe.manifest create mode 100644 app/src/windows/runner/utils.cpp create mode 100644 app/src/windows/runner/utils.h create mode 100644 app/src/windows/runner/win32_window.cpp create mode 100644 app/src/windows/runner/win32_window.h diff --git a/.gitmodules b/.gitmodules index ac3d56f..a6eace9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "build"] path = build url = ../cmaketemplate.git +[submodule "app/src/scaffold"] + path = app/src/scaffold + url = git@github.com:GeniusVentures/openapi-client-scaffold.git diff --git a/GNUS-NEO-SWARM b/GNUS-NEO-SWARM index f2b8047..b388aab 160000 --- a/GNUS-NEO-SWARM +++ b/GNUS-NEO-SWARM @@ -1 +1 @@ -Subproject commit f2b8047748bd440b9c4b74315874220851a2c322 +Subproject commit b388aabea9568dfd4e6d6acbbc7a40f32f532058 diff --git a/app/src/.gitignore b/app/src/.gitignore new file mode 100644 index 0000000..79c113f --- /dev/null +++ b/app/src/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/app/src/.metadata b/app/src/.metadata new file mode 100644 index 0000000..258ecf8 --- /dev/null +++ b/app/src/.metadata @@ -0,0 +1,42 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "fcf2c11572af6f390246c056bc905eca609533a0" + channel: "[user-branch]" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + - platform: android + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + - platform: ios + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + - platform: linux + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + - platform: macos + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + - platform: windows + create_revision: fcf2c11572af6f390246c056bc905eca609533a0 + base_revision: fcf2c11572af6f390246c056bc905eca609533a0 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/app/src/README.md b/app/src/README.md new file mode 100644 index 0000000..24de315 --- /dev/null +++ b/app/src/README.md @@ -0,0 +1,16 @@ +# flutter_app + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/app/src/analysis_options.yaml b/app/src/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/app/src/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/app/src/android/.gitignore b/app/src/android/.gitignore new file mode 100644 index 0000000..be3943c --- /dev/null +++ b/app/src/android/.gitignore @@ -0,0 +1,14 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java +.cxx/ + +# Remember to never publicly share your keystore. +# See https://flutter.dev/to/reference-keystore +key.properties +**/*.keystore +**/*.jks diff --git a/app/src/android/app/build.gradle.kts b/app/src/android/app/build.gradle.kts new file mode 100644 index 0000000..9599a24 --- /dev/null +++ b/app/src/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "ai.gnus.flutter.flutter_app" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "ai.gnus.flutter.flutter_app" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/app/src/android/app/src/debug/AndroidManifest.xml b/app/src/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/app/src/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/android/app/src/main/AndroidManifest.xml b/app/src/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..60ea814 --- /dev/null +++ b/app/src/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/android/app/src/main/kotlin/ai/gnus/flutter/flutter_app/MainActivity.kt b/app/src/android/app/src/main/kotlin/ai/gnus/flutter/flutter_app/MainActivity.kt new file mode 100644 index 0000000..0634368 --- /dev/null +++ b/app/src/android/app/src/main/kotlin/ai/gnus/flutter/flutter_app/MainActivity.kt @@ -0,0 +1,5 @@ +package ai.gnus.flutter.flutter_app + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/app/src/android/app/src/main/res/drawable-v21/launch_background.xml b/app/src/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/app/src/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/app/src/android/app/src/main/res/drawable/launch_background.xml b/app/src/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/app/src/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/app/src/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/app/src/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/app/src/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/app/src/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/app/src/android/app/src/main/res/values-night/styles.xml b/app/src/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/app/src/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/app/src/android/app/src/main/res/values/styles.xml b/app/src/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/app/src/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/app/src/android/app/src/profile/AndroidManifest.xml b/app/src/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/app/src/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/android/build.gradle.kts b/app/src/android/build.gradle.kts new file mode 100644 index 0000000..89176ef --- /dev/null +++ b/app/src/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/app/src/android/gradle.properties b/app/src/android/gradle.properties new file mode 100644 index 0000000..f018a61 --- /dev/null +++ b/app/src/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/app/src/android/gradle/wrapper/gradle-wrapper.properties b/app/src/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ac3b479 --- /dev/null +++ b/app/src/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/app/src/android/settings.gradle.kts b/app/src/android/settings.gradle.kts new file mode 100644 index 0000000..ab39a10 --- /dev/null +++ b/app/src/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.3" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/app/src/ios/.gitignore b/app/src/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/app/src/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/app/src/ios/Flutter/AppFrameworkInfo.plist b/app/src/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..7c56964 --- /dev/null +++ b/app/src/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 12.0 + + diff --git a/app/src/ios/Flutter/Debug.xcconfig b/app/src/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..ec97fc6 --- /dev/null +++ b/app/src/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/app/src/ios/Flutter/Release.xcconfig b/app/src/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..c4855bf --- /dev/null +++ b/app/src/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/app/src/ios/Podfile b/app/src/ios/Podfile new file mode 100644 index 0000000..c9339a0 --- /dev/null +++ b/app/src/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '12.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/app/src/ios/Runner.xcodeproj/project.pbxproj b/app/src/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..d1ff091 --- /dev/null +++ b/app/src/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,616 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = ai.gnus.flutter.flutterApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ai.gnus.flutter.flutterApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ai.gnus.flutter.flutterApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ai.gnus.flutter.flutterApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = ai.gnus.flutter.flutterApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = ai.gnus.flutter.flutterApp; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/app/src/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/app/src/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/app/src/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/app/src/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/app/src/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/app/src/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/app/src/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/app/src/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/app/src/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/app/src/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/app/src/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..e3773d4 --- /dev/null +++ b/app/src/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/ios/Runner.xcworkspace/contents.xcworkspacedata b/app/src/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/app/src/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/app/src/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/app/src/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/app/src/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/app/src/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/app/src/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/app/src/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/app/src/ios/Runner/AppDelegate.swift b/app/src/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..6266644 --- /dev/null +++ b/app/src/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..797d452e458972bab9d994556c8305db4c827017 GIT binary patch literal 406 zcmV;H0crk;P))>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed2d933e1120817fe9182483a228007b18ab6ae GIT binary patch literal 450 zcmV;z0X_bSP)iGWQ_5NJQ_~rNh*z)}eT%KUb z`7gNk0#AwF^#0T0?hIa^`~Ck;!}#m+_uT050aTR(J!bU#|IzRL%^UsMS#KsYnTF*!YeDOytlP4VhV?b} z%rz_<=#CPc)tU1MZTq~*2=8~iZ!lSa<{9b@2Jl;?IEV8)=fG217*|@)CCYgFze-x? zIFODUIA>nWKpE+bn~n7;-89sa>#DR>TSlqWk*!2hSN6D~Qb#VqbP~4Fk&m`@1$JGr zXPIdeRE&b2Thd#{MtDK$px*d3-Wx``>!oimf%|A-&-q*6KAH)e$3|6JV%HX{Hig)k suLT-RhftRq8b9;(V=235Wa|I=027H2wCDra;{X5v07*qoM6N<$f;9x^2LJ#7 literal 0 HcmV?d00001 diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..4cd7b0099ca80c806f8fe495613e8d6c69460d76 GIT binary patch literal 282 zcmV+#0p(^bcu7P-R4C8Q z&e;xxFbF_Vrezo%_kH*OKhshZ6BFpG-Y1e10`QXJKbND7AMQ&cMj60B5TNObaZxYybcN07*qoM6N<$g3m;S%K!iX literal 0 HcmV?d00001 diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fe730945a01f64a61e2235dbe3f45b08f7729182 GIT binary patch literal 462 zcmV;<0WtoGP)-}iV`2<;=$?g5M=KQbZ{F&YRNy7Nn@%_*5{gvDM0aKI4?ESmw z{NnZg)A0R`+4?NF_RZexyVB&^^ZvN!{I28tr{Vje;QNTz`dG&Jz0~Ek&f2;*Z7>B|cg}xYpxEFY+0YrKLF;^Q+-HreN0P{&i zK~zY`?b7ECf-n?@;d<&orQ*Q7KoR%4|C>{W^h6@&01>0SKS`dn{Q}GT%Qj_{PLZ_& zs`MFI#j-(>?bvdZ!8^xTwlY{qA)T4QLbY@j(!YJ7aXJervHy6HaG_2SB`6CC{He}f zHVw(fJWApwPq!6VY7r1w-Fs)@ox~N+q|w~e;JI~C4Vf^@d>Wvj=fl`^u9x9wd9 zR%3*Q+)t%S!MU_`id^@&Y{y7-r98lZX0?YrHlfmwb?#}^1b{8g&KzmkE(L>Z&)179 zp<)v6Y}pRl100G2FL_t(o!|l{-Q-VMg#&MKg7c{O0 z2wJImOS3Gy*Z2Qifdv~JYOp;v+U)a|nLoc7hNH;I$;lzDt$}rkaFw1mYK5_0Q(Sut zvbEloxON7$+HSOgC9Z8ltuC&0OSF!-mXv5caV>#bc3@hBPX@I$58-z}(ZZE!t-aOG zpjNkbau@>yEzH(5Yj4kZiMH32XI!4~gVXNnjAvRx;Sdg^`>2DpUEwoMhTs_st8pKG z(%SHyHdU&v%f36~uERh!bd`!T2dw;z6PrOTQ7Vt*#9F2uHlUVnb#ev_o^fh}Dzmq} zWtlk35}k=?xj28uO|5>>$yXadTUE@@IPpgH`gJ~Ro4>jd1IF|(+IX>8M4Ps{PNvmI zNj4D+XgN83gPt_Gm}`Ybv{;+&yu-C(Grdiahmo~BjG-l&mWM+{e5M1sm&=xduwgM9 z`8OEh`=F3r`^E{n_;%9weN{cf2%7=VzC@cYj+lg>+3|D|_1C@{hcU(DyQG_BvBWe? zvTv``=%b1zrol#=R`JB)>cdjpWt&rLJgVp-t?DREyuq1A%0Z4)6_WsQ7{nzjN zo!X zGXV)2i3kcZIL~_j>uIKPK_zib+3T+Nt3Mb&Br)s)UIaA}@p{wDda>7=Q|mGRp7pqY zkJ!7E{MNz$9nOwoVqpFb)}$IP24Wn2JJ=Cw(!`OXJBr45rP>>AQr$6c7slJWvbpNW z@KTwna6d?PP>hvXCcp=4F;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f*5nx ACIA2c literal 0 HcmV?d00001 diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..502f463a9bc882b461c96aadf492d1729e49e725 GIT binary patch literal 586 zcmV-Q0=4~#P)+}#`wDE{8-2Mebf5<{{PqV{TgVcv*r8?UZ3{-|G?_}T*&y;@cqf{ z{Q*~+qr%%p!1pS*_Uicl#q9lc(D`!D`LN62sNwq{oYw(Wmhk)k<@f$!$@ng~_5)Ru z0Z)trIA5^j{DIW^c+vT2%lW+2<(RtE2wR;4O@)Tm`Xr*?A(qYoM}7i5Yxw>D(&6ou zxz!_Xr~yNF+waPe00049Nkl*;a!v6h%{rlvIH#gW3s8p;bFr=l}mRqpW2h zw=OA%hdyL~z+UHOzl0eKhEr$YYOL-c-%Y<)=j?(bzDweB7{b+%_ypvm_cG{SvM=DK zhv{K@m>#Bw>2W$eUI#iU)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G! zhkE!s;%oku3;IwG3U^2kw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn`0X*2 zy3(k600_CSZj?O$Qu%&$;|TGUJrptR(HzyIx>5E(2r{eA(<6t3e3I0B)7d6s7?Z5J zZ!rtKvA{MiEBm&KFtoifx>5P^Z=vl)95XJn()aS5%ad(s?4-=Tkis9IGu{`Fy8r+H07*qoM6N<$f20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0ec303439225b78712f49115768196d8d76f6790 GIT binary patch literal 862 zcmV-k1EKthP)20Z)wqMt%V?S?~D#06};F zA3KcL`Wb+>5ObvgQIG&ig8(;V04hz?@cqy3{mSh8o!|U|)cI!1_+!fWH@o*8vh^CU z^ws0;(c$gI+2~q^tO#GDHf@=;DncUw00J^eL_t(&-tE|HQ`%4vfZ;WsBqu-$0nu1R zq^Vj;p$clf^?twn|KHO+IGt^q#a3X?w9dXC@*yxhv&l}F322(8Y1&=P&I}~G@#h6; z1CV9ecD9ZEe87{{NtI*)_aJ<`kJa z?5=RBtFF50s;jQLFil-`)m2wrb=6h(&brpj%nG_U&ut~$?8Rokzxi8zJoWr#2dto5 zOX_URcc<1`Iky+jc;A%Vzx}1QU{2$|cKPom2Vf1{8m`vja4{F>HS?^Nc^rp}xo+Nh zxd}eOm`fm3@MQC1< zIk&aCjb~Yh%5+Yq0`)D;q{#-Uqlv*o+Oor zE!I71Z@ASH3grl8&P^L0WpavHoP|UX4e?!igT`4?AZk$hu*@%6WJ;zDOGlw7kj@ zY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f~t1N9smFU literal 0 HcmV?d00001 diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..e9f5fea27c705180eb716271f41b582e76dcbd90 GIT binary patch literal 1674 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 literal 0 HcmV?d00001 diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8 GIT binary patch literal 1418 zcmV;51$Fv~P)q zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+ zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq z^={4hPQv)y=I|4n+?>7Fim=dxt1 z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf` zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_> z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3 zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62( zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;? zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-< z{s<&cCV_1`^TD^ia9!*mQDq& zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H literal 0 HcmV?d00001 diff --git a/app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/app/src/ios/Runner/Base.lproj/LaunchScreen.storyboard b/app/src/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/app/src/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/ios/Runner/Base.lproj/Main.storyboard b/app/src/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/app/src/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/ios/Runner/Info.plist b/app/src/ios/Runner/Info.plist new file mode 100644 index 0000000..22a5e56 --- /dev/null +++ b/app/src/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Flutter App + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + flutter_app + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/app/src/ios/Runner/Runner-Bridging-Header.h b/app/src/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/app/src/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/app/src/ios/RunnerTests/RunnerTests.swift b/app/src/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..86a7c3b --- /dev/null +++ b/app/src/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/app/src/lib/main.dart b/app/src/lib/main.dart new file mode 100644 index 0000000..4a31013 --- /dev/null +++ b/app/src/lib/main.dart @@ -0,0 +1,193 @@ +import 'dart:convert'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_chat_core/flutter_chat_core.dart'; +import 'package:flutter_chat_ui/flutter_chat_ui.dart'; +import 'package:neoswarm_ffi/flutter_slm_bridge.dart'; + +void main() { + // Initialise the SLM engine with the real Mistral-7B MNN model. + geniusSlmInit( + modelPath: + '/Volumes/Work/Gnus_ai/genius-llm-v1/models/mistral-7b-mnn/llm.mnn', + ); + runApp(const GeniusSwarmApp()); +} + +class GeniusSwarmApp extends StatelessWidget { + const GeniusSwarmApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'GNUS NEO SWARM', + debugShowCheckedModeBanner: false, + theme: ThemeData( + colorScheme: ColorScheme.fromSeed( + seedColor: const Color(0xFF6C3CE1), // GNUS purple + brightness: Brightness.dark, + ), + useMaterial3: true, + ), + home: const GeniusChatScreen(), + ); + } +} + +class GeniusChatScreen extends StatefulWidget { + const GeniusChatScreen({super.key}); + + @override + GeniusChatScreenState createState() => GeniusChatScreenState(); +} + +class GeniusChatScreenState extends State { + final _chatController = InMemoryChatController(); + bool _isThinking = false; + bool _modelLoaded = false; + String _engineMode = 'stub'; + + @override + void initState() { + super.initState(); + _checkEngineStatus(); + } + + void _checkEngineStatus() { + final status = getEngineStatus(); + try { + final map = jsonDecode(status) as Map; + setState(() { + _modelLoaded = map['model_loaded'] as bool? ?? false; + _engineMode = map['mode'] as String? ?? 'stub'; + }); + } catch (_) { + setState(() { + _modelLoaded = false; + _engineMode = 'stub'; + }); + } + } + + @override + void dispose() { + _chatController.dispose(); + super.dispose(); + } + + Future _onMessageSend(String text) async { + if (text.trim().isEmpty) return; + + // Insert user message immediately + await _chatController.insertMessage( + TextMessage( + id: _newId(), + authorId: 'user', + createdAt: DateTime.now().toUtc(), + text: text, + ), + ); + + setState(() => _isThinking = true); + + try { + // Build OpenAI v1 request + final requestJson = jsonEncode({ + 'model': 'genius-neo-swarm', + 'messages': [ + {'role': 'user', 'content': text} + ], + }); + + // Call native bridge on a helper isolate (non-blocking) + final responseJson = await chatCompletionsCreateAsync(requestJson); + final content = extractContent(responseJson); + + await _chatController.insertMessage( + TextMessage( + id: _newId(), + authorId: 'assistant', + createdAt: DateTime.now().toUtc(), + text: content, + ), + ); + } catch (e) { + await _chatController.insertMessage( + TextMessage( + id: _newId(), + authorId: 'assistant', + createdAt: DateTime.now().toUtc(), + text: 'Error: $e', + ), + ); + } finally { + setState(() => _isThinking = false); + } + } + + String _newId() => '${Random().nextInt(1 << 30)}'; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('GNUS NEO SWARM'), + actions: [ + if (_isThinking) + const Padding( + padding: EdgeInsets.symmetric(horizontal: 16), + child: Center( + child: SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator(strokeWidth: 2), + ), + ), + ), + ], + ), + body: Column( + children: [ + // Stub mode banner + if (!_modelLoaded) + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), + color: Colors.orange.shade900.withOpacity(0.9), + child: Row( + children: [ + const Icon(Icons.warning_amber_rounded, + color: Colors.orangeAccent, size: 20), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Running in stub mode — no model loaded. ' + 'Responses are simulated. ' + 'Pass a .mnn model path to geniusSlmInit() for real inference.', + style: TextStyle( + color: Colors.orange.shade100, + fontSize: 12, + ), + ), + ), + ], + ), + ), + // Chat area + Expanded( + child: Chat( + chatController: _chatController, + currentUserId: 'user', + onMessageSend: _onMessageSend, + resolveUser: (UserID id) async { + if (id == 'user') return const User(id: 'user', name: 'You'); + return const User(id: 'assistant', name: 'GNUS Swarm'); + }, + ), + ), + ], + ), + ); + } +} diff --git a/app/src/linux/.gitignore b/app/src/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/app/src/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/app/src/linux/CMakeLists.txt b/app/src/linux/CMakeLists.txt new file mode 100644 index 0000000..298fc70 --- /dev/null +++ b/app/src/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "flutter_app") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "ai.gnus.flutter.flutter_app") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/app/src/linux/flutter/CMakeLists.txt b/app/src/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/app/src/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/app/src/linux/flutter/generated_plugin_registrant.cc b/app/src/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..adc5a9f --- /dev/null +++ b/app/src/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,23 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); + file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) record_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "RecordLinuxPlugin"); + record_linux_plugin_register_with_registrar(record_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/app/src/linux/flutter/generated_plugin_registrant.h b/app/src/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/app/src/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/app/src/linux/flutter/generated_plugins.cmake b/app/src/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..1262bd6 --- /dev/null +++ b/app/src/linux/flutter/generated_plugins.cmake @@ -0,0 +1,26 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + file_selector_linux + record_linux + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/app/src/linux/runner/CMakeLists.txt b/app/src/linux/runner/CMakeLists.txt new file mode 100644 index 0000000..e97dabc --- /dev/null +++ b/app/src/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/app/src/linux/runner/main.cc b/app/src/linux/runner/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/app/src/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/app/src/linux/runner/my_application.cc b/app/src/linux/runner/my_application.cc new file mode 100644 index 0000000..9e09737 --- /dev/null +++ b/app/src/linux/runner/my_application.cc @@ -0,0 +1,130 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "flutter_app"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "flutter_app"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/app/src/linux/runner/my_application.h b/app/src/linux/runner/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/app/src/linux/runner/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/app/src/macos/.gitignore b/app/src/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/app/src/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/app/src/macos/Flutter/Flutter-Debug.xcconfig b/app/src/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..4b81f9b --- /dev/null +++ b/app/src/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/app/src/macos/Flutter/Flutter-Release.xcconfig b/app/src/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..5caa9d1 --- /dev/null +++ b/app/src/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/app/src/macos/Flutter/GeneratedPluginRegistrant.swift b/app/src/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..a12a67f --- /dev/null +++ b/app/src/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,24 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import file_selector_macos +import firebase_app_check +import firebase_auth +import firebase_core +import path_provider_foundation +import record_macos +import url_launcher_macos + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + FLTFirebaseAppCheckPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAppCheckPlugin")) + FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) + FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + RecordMacOsPlugin.register(with: registry.registrar(forPlugin: "RecordMacOsPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) +} diff --git a/app/src/macos/Podfile b/app/src/macos/Podfile new file mode 100644 index 0000000..ff5ddb3 --- /dev/null +++ b/app/src/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/app/src/macos/Podfile.lock b/app/src/macos/Podfile.lock new file mode 100644 index 0000000..40d7df2 --- /dev/null +++ b/app/src/macos/Podfile.lock @@ -0,0 +1,156 @@ +PODS: + - AppCheckCore (11.2.0): + - GoogleUtilities/Environment (~> 8.0) + - GoogleUtilities/UserDefaults (~> 8.0) + - PromisesObjC (~> 2.4) + - file_selector_macos (0.0.1): + - FlutterMacOS + - Firebase/AppCheck (12.9.0): + - Firebase/CoreOnly + - FirebaseAppCheck (~> 12.9.0) + - Firebase/Auth (12.9.0): + - Firebase/CoreOnly + - FirebaseAuth (~> 12.9.0) + - Firebase/CoreOnly (12.9.0): + - FirebaseCore (~> 12.9.0) + - firebase_app_check (0.4.1-5): + - Firebase/AppCheck (~> 12.9.0) + - Firebase/CoreOnly (~> 12.9.0) + - firebase_core + - FlutterMacOS + - firebase_auth (6.2.0): + - Firebase/Auth (~> 12.9.0) + - Firebase/CoreOnly (~> 12.9.0) + - firebase_core + - FlutterMacOS + - firebase_core (4.5.0): + - Firebase/CoreOnly (~> 12.9.0) + - FlutterMacOS + - FirebaseAppCheck (12.9.0): + - AppCheckCore (~> 11.0) + - FirebaseAppCheckInterop (~> 12.9.0) + - FirebaseCore (~> 12.9.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/UserDefaults (~> 8.1) + - FirebaseAppCheckInterop (12.9.0) + - FirebaseAuth (12.9.0): + - FirebaseAppCheckInterop (~> 12.9.0) + - FirebaseAuthInterop (~> 12.9.0) + - FirebaseCore (~> 12.9.0) + - FirebaseCoreExtension (~> 12.9.0) + - GoogleUtilities/AppDelegateSwizzler (~> 8.1) + - GoogleUtilities/Environment (~> 8.1) + - GTMSessionFetcher/Core (< 6.0, >= 3.4) + - RecaptchaInterop (~> 101.0) + - FirebaseAuthInterop (12.9.0) + - FirebaseCore (12.9.0): + - FirebaseCoreInternal (~> 12.9.0) + - GoogleUtilities/Environment (~> 8.1) + - GoogleUtilities/Logger (~> 8.1) + - FirebaseCoreExtension (12.9.0): + - FirebaseCore (~> 12.9.0) + - FirebaseCoreInternal (12.9.0): + - "GoogleUtilities/NSData+zlib (~> 8.1)" + - FlutterMacOS (1.0.0) + - GoogleUtilities/AppDelegateSwizzler (8.1.0): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Privacy + - GoogleUtilities/Environment (8.1.0): + - GoogleUtilities/Privacy + - GoogleUtilities/Logger (8.1.0): + - GoogleUtilities/Environment + - GoogleUtilities/Privacy + - GoogleUtilities/Network (8.1.0): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Privacy + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (8.1.0)": + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (8.1.0) + - GoogleUtilities/Reachability (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GoogleUtilities/UserDefaults (8.1.0): + - GoogleUtilities/Logger + - GoogleUtilities/Privacy + - GTMSessionFetcher/Core (5.3.0) + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS + - PromisesObjC (2.4.0) + - record_macos (1.2.0): + - FlutterMacOS + - url_launcher_macos (0.0.1): + - FlutterMacOS + +DEPENDENCIES: + - file_selector_macos (from `Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos`) + - firebase_app_check (from `Flutter/ephemeral/.symlinks/plugins/firebase_app_check/macos`) + - firebase_auth (from `Flutter/ephemeral/.symlinks/plugins/firebase_auth/macos`) + - firebase_core (from `Flutter/ephemeral/.symlinks/plugins/firebase_core/macos`) + - FlutterMacOS (from `Flutter/ephemeral`) + - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) + - record_macos (from `Flutter/ephemeral/.symlinks/plugins/record_macos/macos`) + - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + +SPEC REPOS: + trunk: + - AppCheckCore + - Firebase + - FirebaseAppCheck + - FirebaseAppCheckInterop + - FirebaseAuth + - FirebaseAuthInterop + - FirebaseCore + - FirebaseCoreExtension + - FirebaseCoreInternal + - GoogleUtilities + - GTMSessionFetcher + - PromisesObjC + +EXTERNAL SOURCES: + file_selector_macos: + :path: Flutter/ephemeral/.symlinks/plugins/file_selector_macos/macos + firebase_app_check: + :path: Flutter/ephemeral/.symlinks/plugins/firebase_app_check/macos + firebase_auth: + :path: Flutter/ephemeral/.symlinks/plugins/firebase_auth/macos + firebase_core: + :path: Flutter/ephemeral/.symlinks/plugins/firebase_core/macos + FlutterMacOS: + :path: Flutter/ephemeral + path_provider_foundation: + :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin + record_macos: + :path: Flutter/ephemeral/.symlinks/plugins/record_macos/macos + url_launcher_macos: + :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + +SPEC CHECKSUMS: + AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f + file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 + Firebase: 065f2bb395062046623036d8e6dc857bc2521d56 + firebase_app_check: 1ea404b52b0910bf632b1ea2e00ceb8d1730cb44 + firebase_auth: def738277a306ec5676724b44ce49b37edd3bc91 + firebase_core: c74b220e9288decea6bed17399c249734a7e76d2 + FirebaseAppCheck: 94dae4d9bb682bdef85a778b0c1024a4613f1e89 + FirebaseAppCheckInterop: 4bade10286cc977e516f75d2d8312cbdfa534789 + FirebaseAuth: 3a39f6436c21ebfd7919b698228b4f89ff94c23b + FirebaseAuthInterop: f8f6ff72dc24621906497fbe5cf3c42ee815e59c + FirebaseCore: 428912f751178b06bef0a1793effeb4a5e09a9b8 + FirebaseCoreExtension: e911052d59cd0da237a45d706fc0f81654f035c1 + FirebaseCoreInternal: b321eafae5362113bc182956fafc9922cfc77b72 + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 + GTMSessionFetcher: 127211aeec0b1e904fc49f4f6f895dcc535b0ecf + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 + record_macos: 7f227161b93c49e7e34fe681c5891c8622c8cc8b + url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 + +PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 + +COCOAPODS: 1.16.2 diff --git a/app/src/macos/Runner.xcodeproj/project.pbxproj b/app/src/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..c0a0453 --- /dev/null +++ b/app/src/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,801 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + A5D79D5E6288FED3A5C536E5 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7E7E148897E623BC7E6571DA /* Pods_RunnerTests.framework */; }; + E6BCBC92667CE182165DCED9 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B61100380FDF2B911A362A68 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* flutter_app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = flutter_app.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 523F86F537CC89A02E2CE0E3 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 7E7E148897E623BC7E6571DA /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 9C2E17A68D31BAB2B9E68581 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + B61100380FDF2B911A362A68 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B640401C13E43EDFFA6D93C6 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + C7BB2BB546777500694A7960 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + CDE08E569F83A3E7D0B6DA25 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + EC3C4DFEB31B602CDD46FB3A /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A5D79D5E6288FED3A5C536E5 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E6BCBC92667CE182165DCED9 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 6F53EFE9F296797FE6782602 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* flutter_app.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 6F53EFE9F296797FE6782602 /* Pods */ = { + isa = PBXGroup; + children = ( + CDE08E569F83A3E7D0B6DA25 /* Pods-Runner.debug.xcconfig */, + EC3C4DFEB31B602CDD46FB3A /* Pods-Runner.release.xcconfig */, + B640401C13E43EDFFA6D93C6 /* Pods-Runner.profile.xcconfig */, + C7BB2BB546777500694A7960 /* Pods-RunnerTests.debug.xcconfig */, + 9C2E17A68D31BAB2B9E68581 /* Pods-RunnerTests.release.xcconfig */, + 523F86F537CC89A02E2CE0E3 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + B61100380FDF2B911A362A68 /* Pods_Runner.framework */, + 7E7E148897E623BC7E6571DA /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 22B6799C865657AAE01D8C72 /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 0D326D758AA1BFC7D8F8A1E8 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 346E664EA26D1E43D7B89D11 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* flutter_app.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0D326D758AA1BFC7D8F8A1E8 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 22B6799C865657AAE01D8C72 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 346E664EA26D1E43D7B89D11 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = C7BB2BB546777500694A7960 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ai.gnus.flutter.flutterApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/flutter_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/flutter_app"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9C2E17A68D31BAB2B9E68581 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ai.gnus.flutter.flutterApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/flutter_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/flutter_app"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 523F86F537CC89A02E2CE0E3 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = ai.gnus.flutter.flutterApp.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/flutter_app.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/flutter_app"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/app/src/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/app/src/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/app/src/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/app/src/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/app/src/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..1a7ee8e --- /dev/null +++ b/app/src/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/macos/Runner.xcworkspace/contents.xcworkspacedata b/app/src/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..21a3cc1 --- /dev/null +++ b/app/src/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/app/src/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/app/src/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/app/src/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/app/src/macos/Runner/AppDelegate.swift b/app/src/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..b3c1761 --- /dev/null +++ b/app/src/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..82b6f9d9a33e198f5747104729e1fcef999772a5 GIT binary patch literal 102994 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ⅅ8MglG&ER{i5lN315cO?EdHNLrg? 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 z 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 Pe7HEdAEg|=c=LY&YVNkY literal 0 HcmV?d00001 diff --git a/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..13b35eba55c6dabc3aac36f33d859266c18fa0d0 GIT binary patch literal 5680 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 literal 0 HcmV?d00001 diff --git a/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3f5fa40fb3d1e0710331a48de5d256da3f275d GIT binary patch literal 520 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 literal 0 HcmV?d00001 diff --git a/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000000000000000000000000000000000000..2f1632cfddf3d9dade342351e627a0a75609fb46 GIT binary patch literal 2218 zcmV;b2vzrqP)Px#L}ge>W=%~1DgXcg2mk?xX#fNO00031000^Q000001E2u_0{{R30RRC20H6W@ z1ONa40RR91K%fHv1ONa40RR91KmY&$07g+lumAuE6iGxuRCodHTWf3-RTMruyW6Fu zQYeUM04eX6D5c0FCjKKPrco1(K`<0SL=crI{PC3-^hZU0kQie$gh-5!7z6SH6Q0J% zqot*`H1q{R5fHFYS}dje@;kG=v$L0(yY0?wY2%*c?A&{2?!D*x?m71{of2gv!$5|C z3>qG_BW}7K_yUcT3A5C6QD<+{aq?x;MAUyAiJn#Jv8_zZtQ{P zTRzbL3U9!qVuZzS$xKU10KiW~Bgdcv1-!uAhQxf3a7q+dU6lj?yoO4Lq4TUN4}h{N z*fIM=SS8|C2$(T>w$`t@3Tka!(r!7W`x z-isCVgQD^mG-MJ;XtJuK3V{Vy72GQ83KRWsHU?e*wrhKk=ApIYeDqLi;JI1e zuvv}5^Dc=k7F7?nm3nIw$NVmU-+R>> zyqOR$-2SDpJ}Pt;^RkJytDVXNTsu|mI1`~G7yw`EJR?VkGfNdqK9^^8P`JdtTV&tX4CNcV4 z&N06nZa??Fw1AgQOUSE2AmPE@WO(Fvo`%m`cDgiv(fAeRA%3AGXUbsGw{7Q`cY;1BI#ac3iN$$Hw z0LT0;xc%=q)me?Y*$xI@GRAw?+}>=9D+KTk??-HJ4=A>`V&vKFS75@MKdSF1JTq{S zc1!^8?YA|t+uKigaq!sT;Z!&0F2=k7F0PIU;F$leJLaw2UI6FL^w}OG&!;+b%ya1c z1n+6-inU<0VM-Y_s5iTElq)ThyF?StVcebpGI znw#+zLx2@ah{$_2jn+@}(zJZ{+}_N9BM;z)0yr|gF-4=Iyu@hI*Lk=-A8f#bAzc9f z`Kd6K--x@t04swJVC3JK1cHY-Hq+=|PN-VO;?^_C#;coU6TDP7Bt`;{JTG;!+jj(` zw5cLQ-(Cz-Tlb`A^w7|R56Ce;Wmr0)$KWOUZ6ai0PhzPeHwdl0H(etP zUV`va_i0s-4#DkNM8lUlqI7>YQLf)(lz9Q3Uw`)nc(z3{m5ZE77Ul$V%m)E}3&8L0 z-XaU|eB~Is08eORPk;=<>!1w)Kf}FOVS2l&9~A+@R#koFJ$Czd%Y(ENTV&A~U(IPI z;UY+gf+&6ioZ=roly<0Yst8ck>(M=S?B-ys3mLdM&)ex!hbt+ol|T6CTS+Sc0jv(& z7ijdvFwBq;0a{%3GGwkDKTeG`b+lyj0jjS1OMkYnepCdoosNY`*zmBIo*981BU%%U z@~$z0V`OVtIbEx5pa|Tct|Lg#ZQf5OYMUMRD>Wdxm5SAqV2}3!ceE-M2 z@O~lQ0OiKQp}o9I;?uxCgYVV?FH|?Riri*U$Zi_`V2eiA>l zdSm6;SEm6#T+SpcE8Ro_f2AwxzI z44hfe^WE3!h@W3RDyA_H440cpmYkv*)6m1XazTqw%=E5Xv7^@^^T7Q2wxr+Z2kVYr + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/macos/Runner/Configs/AppInfo.xcconfig b/app/src/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..53517eb --- /dev/null +++ b/app/src/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = flutter_app + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = ai.gnus.flutter.flutterApp + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 ai.gnus.flutter. All rights reserved. diff --git a/app/src/macos/Runner/Configs/Debug.xcconfig b/app/src/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/app/src/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/app/src/macos/Runner/Configs/Release.xcconfig b/app/src/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/app/src/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/app/src/macos/Runner/Configs/Warnings.xcconfig b/app/src/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/app/src/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/app/src/macos/Runner/DebugProfile.entitlements b/app/src/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/app/src/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/app/src/macos/Runner/Info.plist b/app/src/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/app/src/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/app/src/macos/Runner/MainFlutterWindow.swift b/app/src/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..3cc05eb --- /dev/null +++ b/app/src/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/app/src/macos/Runner/Release.entitlements b/app/src/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/app/src/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/app/src/macos/RunnerTests/RunnerTests.swift b/app/src/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..61f3bd1 --- /dev/null +++ b/app/src/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/app/src/pubspec.lock b/app/src/pubspec.lock new file mode 100644 index 0000000..a498ae3 --- /dev/null +++ b/app/src/pubspec.lock @@ -0,0 +1,866 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: afe15ce18a287d2f89da95566e62892df339b1936bbe9b83587df45b944ee72a + url: "https://pub.dev" + source: hosted + version: "1.3.67" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + url: "https://pub.dev" + source: hosted + version: "2.13.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + camera: + dependency: transitive + description: + name: camera + sha256: "87a27e0553e3432119c1c2f6e4b9a1bbf7d2c660552b910bfa59185a9facd632" + url: "https://pub.dev" + source: hosted + version: "0.11.2+1" + camera_android_camerax: + dependency: transitive + description: + name: camera_android_camerax + sha256: "58b8fe843a3c83fd1273c00cb35f5a8ae507f6cc9b2029bcf7e2abba499e28d8" + url: "https://pub.dev" + source: hosted + version: "0.6.19+1" + camera_avfoundation: + dependency: transitive + description: + name: camera_avfoundation + sha256: "951ef122d01ebba68b7a54bfe294e8b25585635a90465c311b2f875ae72c412f" + url: "https://pub.dev" + source: hosted + version: "0.9.21+2" + camera_platform_interface: + dependency: transitive + description: + name: camera_platform_interface + sha256: "98cfc9357e04bad617671b4c1f78a597f25f08003089dd94050709ae54effc63" + url: "https://pub.dev" + source: hosted + version: "2.12.0" + camera_web: + dependency: transitive + description: + name: camera_web + sha256: "57f49a635c8bf249d07fb95eb693d7e4dda6796dedb3777f9127fb54847beba7" + url: "https://pub.dev" + source: hosted + version: "0.3.5+3" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" + url: "https://pub.dev" + source: hosted + version: "0.3.5+2" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + url: "https://pub.dev" + source: hosted + version: "1.0.8" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + ffi: + dependency: "direct main" + description: + name: ffi + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + file_selector: + dependency: transitive + description: + name: file_selector + sha256: "5f1d15a7f17115038f433d1b0ea57513cc9e29a9d5338d166cb0bef3fa90a7a0" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + file_selector_android: + dependency: transitive + description: + name: file_selector_android + sha256: "1ce58b609289551f8ec07265476720e77d19764339cc1d8e4df3c4d34dac6499" + url: "https://pub.dev" + source: hosted + version: "0.5.1+17" + file_selector_ios: + dependency: transitive + description: + name: file_selector_ios + sha256: fe9f52123af16bba4ad65bd7e03defbbb4b172a38a8e6aaa2a869a0c56a5f5fb + url: "https://pub.dev" + source: hosted + version: "0.5.3+2" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" + url: "https://pub.dev" + source: hosted + version: "0.9.4" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "19124ff4a3d8864fdc62072b6a2ef6c222d55a3404fe14893a3c02744907b60c" + url: "https://pub.dev" + source: hosted + version: "0.9.4+4" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + file_selector_web: + dependency: transitive + description: + name: file_selector_web + sha256: c4c0ea4224d97a60a7067eca0c8fd419e708ff830e0c83b11a48faf566cec3e7 + url: "https://pub.dev" + source: hosted + version: "0.9.4+2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" + url: "https://pub.dev" + source: hosted + version: "0.9.3+5" + firebase_ai: + dependency: transitive + description: + name: firebase_ai + sha256: "3ce533f058ceae27c5ce033c09e6b80c7d9b02a0edd66bdc7b5bb721345786f1" + url: "https://pub.dev" + source: hosted + version: "3.9.0" + firebase_app_check: + dependency: transitive + description: + name: firebase_app_check + sha256: cc0bfeb003c5747e963f4f378e72c8c5c5f91a243f2e85b7da29ff42f4709996 + url: "https://pub.dev" + source: hosted + version: "0.4.1+5" + firebase_app_check_platform_interface: + dependency: transitive + description: + name: firebase_app_check_platform_interface + sha256: "54166319f9121bd94b2374864e84dd54507438095fc2d42ca49f23957b16b0d1" + url: "https://pub.dev" + source: hosted + version: "0.2.1+5" + firebase_app_check_web: + dependency: transitive + description: + name: firebase_app_check_web + sha256: f8dddba09bd7786e017b58e74455886106ac7ca9e798769e2cac3e988fc2d146 + url: "https://pub.dev" + source: hosted + version: "0.2.2+3" + firebase_auth: + dependency: transitive + description: + name: firebase_auth + sha256: "1c290de59ba88d3b193e5933441ea4793d623e802d75bd4135e36d550c3f6b62" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: c830e2a1c69c27242a920296784458ad6eb71decdfa083578f7788dbde5d3a69 + url: "https://pub.dev" + source: hosted + version: "8.1.7" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: "809d0807a7b6dbdd2d2dd04f217375aaa9835794750a4eec408c2990ed505e41" + url: "https://pub.dev" + source: hosted + version: "6.1.3" + firebase_core: + dependency: transitive + description: + name: firebase_core + sha256: f0997fee80fbb6d2c658c5b88ae87ba1f9506b5b37126db64fc2e75d8e977fbb + url: "https://pub.dev" + source: hosted + version: "4.5.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: cccb4f572325dc14904c02fcc7db6323ad62ba02536833dddb5c02cac7341c64 + url: "https://pub.dev" + source: hosted + version: "6.0.2" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "856ca92bf2d75a63761286ab8e791bda3a85184c2b641764433b619647acfca6" + url: "https://pub.dev" + source: hosted + version: "3.5.0" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_ai_toolkit: + dependency: "direct main" + description: + name: flutter_ai_toolkit + sha256: "070a27e5ceb7154d15dffd165b3f7d60ec12d0a78b4d7a4063466c5657e13843" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_context_menu: + dependency: transitive + description: + name: flutter_context_menu + sha256: "7040c03fb9cb2a113282d836edadf32b6cdff67cccdf3d158e7411ecc15d7712" + url: "https://pub.dev" + source: hosted + version: "0.4.2" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_markdown_plus: + dependency: transitive + description: + name: flutter_markdown_plus + sha256: "039177906850278e8fb1cd364115ee0a46281135932fa8ecea8455522166d2de" + url: "https://pub.dev" + source: hosted + version: "1.0.7" + flutter_picture_taker: + dependency: transitive + description: + name: flutter_picture_taker + sha256: d24d4c10e42324832b550bd59d1fe84129e860b75b4b2d57d6b398a41fd5dc9a + url: "https://pub.dev" + source: hosted + version: "0.2.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: c2fe1001710127dfa7da89977a08d591398370d099aacdaa6d44da7eb14b8476 + url: "https://pub.dev" + source: hosted + version: "2.0.31" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + google_fonts: + dependency: transitive + description: + name: google_fonts + sha256: "517b20870220c48752eafa0ba1a797a092fb22df0d89535fd9991e86ee2cdd9c" + url: "https://pub.dev" + source: hosted + version: "6.3.2" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + image_picker: + dependency: transitive + description: + name: image_picker + sha256: "784210112be18ea55f69d7076e2c656a4e24949fa9e76429fe53af0c0f4fa320" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "28f3987ca0ec702d346eae1d90eda59603a2101b52f1e234ded62cff1d5cfa6e" + url: "https://pub.dev" + source: hosted + version: "0.8.13+1" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: eb06fe30bab4c4497bad449b66448f50edcc695f1c59408e78aa3a8059eb8f0e + url: "https://pub.dev" + source: hosted + version: "0.8.13" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: d58cd9d67793d52beefd6585b12050af0a7663c0c2a6ece0fb110a35d6955e04 + url: "https://pub.dev" + source: hosted + version: "0.2.2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae + url: "https://pub.dev" + source: hosted + version: "0.2.2" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 + url: "https://pub.dev" + source: hosted + version: "5.1.1" + markdown: + dependency: transitive + description: + name: markdown + sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1" + url: "https://pub.dev" + source: hosted + version: "7.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" + source: hosted + version: "0.12.19" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "3b4c1fc3aa55ddc9cd4aa6759984330d5c8e66aa7702a6223c61540dc6380c37" + url: "https://pub.dev" + source: hosted + version: "2.2.19" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + record: + dependency: transitive + description: + name: record + sha256: d5b6b334f3ab02460db6544e08583c942dbf23e3504bf1e14fd4cbe3d9409277 + url: "https://pub.dev" + source: hosted + version: "6.2.0" + record_android: + dependency: transitive + description: + name: record_android + sha256: "94783f08403aed33ffb68797bf0715b0812eb852f3c7985644c945faea462ba1" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + record_ios: + dependency: transitive + description: + name: record_ios + sha256: "8df7c136131bd05efc19256af29b2ba6ccc000ccc2c80d4b6b6d7a8d21a3b5a9" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + record_linux: + dependency: transitive + description: + name: record_linux + sha256: c31a35cc158cd666fc6395f7f56fc054f31685571684be6b97670a27649ce5c7 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + record_macos: + dependency: transitive + description: + name: record_macos + sha256: "084902e63fc9c0c224c29203d6c75f0bdf9b6a40536c9d916393c8f4c4256488" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + record_platform_interface: + dependency: transitive + description: + name: record_platform_interface + sha256: "8a81dbc4e14e1272a285bbfef6c9136d070a47d9b0d1f40aa6193516253ee2f6" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + record_web: + dependency: transitive + description: + name: record_web + sha256: "7e9846981c1f2d111d86f0ae3309071f5bba8b624d1c977316706f08fc31d16d" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + record_windows: + dependency: transitive + description: + name: record_windows + sha256: "223258060a1d25c62bae18282c16783f28581ec19401d17e56b5205b9f039d78" + url: "https://pub.dev" + source: hosted + version: "1.0.7" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" + source: hosted + version: "1.10.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" + source: hosted + version: "0.7.10" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + url_launcher: + dependency: transitive + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e" + url: "https://pub.dev" + source: hosted + version: "6.3.20" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 + url: "https://pub.dev" + source: hosted + version: "6.3.4" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f + url: "https://pub.dev" + source: hosted + version: "3.2.3" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" + uuid: + dependency: transitive + description: + name: uuid + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + url: "https://pub.dev" + source: hosted + version: "4.5.3" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + url: "https://pub.dev" + source: hosted + version: "15.0.0" + waveform_flutter: + dependency: transitive + description: + name: waveform_flutter + sha256: "08c9e98d4cf119428d8b3c083ed42c11c468623eaffdf30420ae38e36662922a" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + waveform_recorder: + dependency: transitive + description: + name: waveform_recorder + sha256: "1ca0a19b143d1bdef2adfb3d28f0627c18aee5285235c8cf81a89bf29a0420e1" + url: "https://pub.dev" + source: hosted + version: "1.8.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" +sdks: + dart: ">=3.9.0-0 <4.0.0" + flutter: ">=3.32.0" diff --git a/app/src/pubspec.yaml b/app/src/pubspec.yaml new file mode 100644 index 0000000..3ab347d --- /dev/null +++ b/app/src/pubspec.yaml @@ -0,0 +1,96 @@ +name: flutter_app +description: "GNUS NEO SWARM — Genius AI swarm intelligence app" +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.8.1 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + ffi: ^2.2.0 + flutter_chat_core: ^2.0.0 + flutter_chat_ui: ^2.0.0 + neoswarm_ffi: + path: ../GNUS-NEO-SWARM/neoswarm_ffi + frontend_scaffold: + path: scaffold + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^5.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/app/src/scaffold b/app/src/scaffold new file mode 160000 index 0000000..72f88e0 --- /dev/null +++ b/app/src/scaffold @@ -0,0 +1 @@ +Subproject commit 72f88e0430e31b4c6a4e47b7fd6ad977b86f47ea diff --git a/app/src/test/widget_test.dart b/app/src/test/widget_test.dart new file mode 100644 index 0000000..be73888 --- /dev/null +++ b/app/src/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:flutter_app/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/app/src/windows/.gitignore b/app/src/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/app/src/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/app/src/windows/CMakeLists.txt b/app/src/windows/CMakeLists.txt new file mode 100644 index 0000000..8b59335 --- /dev/null +++ b/app/src/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(flutter_app LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "flutter_app") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/app/src/windows/flutter/CMakeLists.txt b/app/src/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..903f489 --- /dev/null +++ b/app/src/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/app/src/windows/flutter/generated_plugin_registrant.cc b/app/src/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..3c9a7f7 --- /dev/null +++ b/app/src/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,26 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); + FirebaseAuthPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseAuthPluginCApi")); + FirebaseCorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); + RecordWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("RecordWindowsPluginCApi")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/app/src/windows/flutter/generated_plugin_registrant.h b/app/src/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/app/src/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/app/src/windows/flutter/generated_plugins.cmake b/app/src/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..f2ab310 --- /dev/null +++ b/app/src/windows/flutter/generated_plugins.cmake @@ -0,0 +1,28 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + file_selector_windows + firebase_auth + firebase_core + record_windows + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/app/src/windows/runner/CMakeLists.txt b/app/src/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/app/src/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/app/src/windows/runner/Runner.rc b/app/src/windows/runner/Runner.rc new file mode 100644 index 0000000..bccd84f --- /dev/null +++ b/app/src/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "ai.gnus.flutter" "\0" + VALUE "FileDescription", "flutter_app" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "flutter_app" "\0" + VALUE "LegalCopyright", "Copyright (C) 2026 ai.gnus.flutter. All rights reserved." "\0" + VALUE "OriginalFilename", "flutter_app.exe" "\0" + VALUE "ProductName", "flutter_app" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/app/src/windows/runner/flutter_window.cpp b/app/src/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..955ee30 --- /dev/null +++ b/app/src/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/app/src/windows/runner/flutter_window.h b/app/src/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/app/src/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/app/src/windows/runner/main.cpp b/app/src/windows/runner/main.cpp new file mode 100644 index 0000000..69a6d9f --- /dev/null +++ b/app/src/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"flutter_app", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/app/src/windows/runner/resource.h b/app/src/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/app/src/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/app/src/windows/runner/resources/app_icon.ico b/app/src/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c04e20caf6370ebb9253ad831cc31de4a9c965f6 GIT binary patch literal 33772 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 literal 0 HcmV?d00001 diff --git a/app/src/windows/runner/runner.exe.manifest b/app/src/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..153653e --- /dev/null +++ b/app/src/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/app/src/windows/runner/utils.cpp b/app/src/windows/runner/utils.cpp new file mode 100644 index 0000000..3a0b465 --- /dev/null +++ b/app/src/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/app/src/windows/runner/utils.h b/app/src/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/app/src/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/app/src/windows/runner/win32_window.cpp b/app/src/windows/runner/win32_window.cpp new file mode 100644 index 0000000..60608d0 --- /dev/null +++ b/app/src/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/app/src/windows/runner/win32_window.h b/app/src/windows/runner/win32_window.h new file mode 100644 index 0000000..e901dde --- /dev/null +++ b/app/src/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ From 9ac490b950dcd69ba8e1e074fa41ad461b0735aa Mon Sep 17 00:00:00 2001 From: Super Genius Date: Sat, 15 Aug 2026 17:21:57 -0700 Subject: [PATCH 02/49] =?UTF-8?q?docs:=20capture=20exploration=20=E2=80=94?= =?UTF-8?q?=20GCS=20chat=20architecture?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .planning/notes/gcs-chat-architecture.md | 50 +++++++++++++++++++ .planning/seeds/private-room-key-rotation.md | 22 ++++++++ .../define-gcs-bot-identity-mapping.md | 26 ++++++++++ .../pending/design-message-crdt-schema.md | 26 ++++++++++ src/__init__.py | 0 src/{ => lib}/gcs_core.cpp | 0 6 files changed, 124 insertions(+) create mode 100644 .planning/notes/gcs-chat-architecture.md create mode 100644 .planning/seeds/private-room-key-rotation.md create mode 100644 .planning/todos/pending/define-gcs-bot-identity-mapping.md create mode 100644 .planning/todos/pending/design-message-crdt-schema.md create mode 100644 src/__init__.py rename src/{ => lib}/gcs_core.cpp (100%) diff --git a/.planning/notes/gcs-chat-architecture.md b/.planning/notes/gcs-chat-architecture.md new file mode 100644 index 0000000..488e566 --- /dev/null +++ b/.planning/notes/gcs-chat-architecture.md @@ -0,0 +1,50 @@ +--- +title: GCS Chat Architecture +date: 2026-08-15 +context: gs-explore --ws app +--- + +# GCS Chat Architecture + +Multi-party group chat where one participant is the GCS system (ELM router / specialist models). + +## Room Types + +Single unified model. Per-participant `autoAnswer` policy controls bot behavior: + +| Policy | Trigger | Use Case | +|--------|---------|----------| +| **None** | `@gcs` mention only | Multi-party human chat | +| **GCS** | Every message | Q&A mode, solo chat | + +## Pub/Sub Topics + +- **Room topic:** `ipfs-pubsub://gcs/chat/` +- **Lobby topic:** `ipfs-pubsub://gcs/chat/lobby` — public room registry (CRDT) + +Public rooms publish existence to lobby. Private rooms never publish to lobby. + +## Privacy + +App-layer encryption (not libp2p PSK). Per-room symmetric key encrypts message payloads before CRDT publish. Existence visible on pubsub network, content hidden. Key rotation deferred to post-MVP. + +## Message Flow + +1. User sends message → Flutter Cubit → FFI → C++ GCS core +2. C++ publishes encrypted payload to room topic via GossipPubSub +3. CRDT (GlobalDB) syncs op to all room participants +4. GCS bot participant filters message per `autoAnswer` policy +5. If triggered, bot generates response → publishes as new op + +## MVP Scope + +- Room creation (name → topic) +- Message send/receive with CRDT sync +- GCS bot participant with mention/every-message auto-answer +- No encryption yet (public rooms only) + +## Deferred + +- Private room key rotation +- Room archive / lifecycle management +- Room discovery beyond lobby (search, QR codes) diff --git a/.planning/seeds/private-room-key-rotation.md b/.planning/seeds/private-room-key-rotation.md new file mode 100644 index 0000000..69547d8 --- /dev/null +++ b/.planning/seeds/private-room-key-rotation.md @@ -0,0 +1,22 @@ +--- +title: Private room key rotation +date: 2026-08-15 +trigger_condition: MVP chat sync proven working, private rooms requested +context: gs-explore --ws app +--- + +# Private room key rotation + +After MVP proves basic CRDT sync, add app-layer encryption for private rooms. + +## Scope + +- Per-room symmetric key generation and distribution +- Key rotation when participants join/leave +- Encrypted payload format (nonce + ciphertext + tag) +- Key storage on device (secure enclave / keychain) + +## Out of scope (future) + +- Forward secrecy (requires async key agreement) +- Post-compromise security diff --git a/.planning/todos/pending/define-gcs-bot-identity-mapping.md b/.planning/todos/pending/define-gcs-bot-identity-mapping.md new file mode 100644 index 0000000..cf23ec8 --- /dev/null +++ b/.planning/todos/pending/define-gcs-bot-identity-mapping.md @@ -0,0 +1,26 @@ +--- +title: Define GCS bot participant identity mapping +date: 2026-08-15 +priority: high +context: gs-explore --ws app +--- + +# Define GCS bot participant identity mapping + +How does the GCS bot participant in a chat room map to the ELM router's specialist selection? + +## Questions to resolve + +- Is there one GCS bot per room, or does the bot multiplex across rooms? +- How does a `@gcs` mention route to the correct specialist (knowledge, code, creative)? +- Does the bot participant have a persistent identity (same ID across rooms) or ephemeral per-room? +- How does the bot's auto-answer policy interact with specialist selection? + +## Dependencies + +- ELM router from GNUS-NEO-SWARM (Phase 7) +- Chat room participant model + +## Acceptance + +Identity mapping documented, with clear flow from mention → specialist selection → response generation. diff --git a/.planning/todos/pending/design-message-crdt-schema.md b/.planning/todos/pending/design-message-crdt-schema.md new file mode 100644 index 0000000..1a72e98 --- /dev/null +++ b/.planning/todos/pending/design-message-crdt-schema.md @@ -0,0 +1,26 @@ +--- +title: Design message CRDT schema for chat room ops +date: 2026-08-15 +priority: high +context: gs-explore --ws app +--- + +# Design message CRDT schema + +Define the CRDT operation structure for chat messages. + +## Questions to resolve + +- What fields does a message op carry? (sender, timestamp, content, room, reply-to?) +- How do we handle message ordering across clients? (CRDT vector clocks, or simple lamport timestamp?) +- Do we need message deletion/editing ops, or append-only? +- How does the schema accommodate encrypted payloads for private rooms? + +## Dependencies + +- GCS GlobalDB CRDT from Phase 3 (neoswarm workstream) +- GossipPubSub topic structure from architecture note + +## Acceptance + +Schema documented in `src/lib/` or planning docs, with example ops for create/edit/delete. diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/gcs_core.cpp b/src/lib/gcs_core.cpp similarity index 100% rename from src/gcs_core.cpp rename to src/lib/gcs_core.cpp From b9aefb2000e533729303c24e6f72b88dc1af4632 Mon Sep 17 00:00:00 2001 From: Super Genius Date: Sat, 15 Aug 2026 17:26:54 -0700 Subject: [PATCH 03/49] =?UTF-8?q?docs:=20expand=20GCS=20chat=20architectur?= =?UTF-8?q?e=20=E2=80=94=20spaces,=20room=20invites,=20auto-join=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .planning/notes/gcs-chat-architecture.md | 52 ++++++++++++++++++------ 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/.planning/notes/gcs-chat-architecture.md b/.planning/notes/gcs-chat-architecture.md index 488e566..ac708d5 100644 --- a/.planning/notes/gcs-chat-architecture.md +++ b/.planning/notes/gcs-chat-architecture.md @@ -8,7 +8,37 @@ context: gs-explore --ws app Multi-party group chat where one participant is the GCS system (ELM router / specialist models). -## Room Types +## Entity Hierarchy + +| Layer | Purpose | Topic | Access | +|-------|---------|-------|--------| +| **Space** | Container, invite management, room grouping | `gcs/spaces/` | Public or private | +| **Room** | Chat, message sync | `gcs/chat/` | Public or private | +| **DM** | 1:1 private chat | `gcs/chat/` | Private (auto-generated) | +| **Lobby** | Global discovery of public spaces | `gcs/spaces/lobby` | Public registry | + +## Space Configuration + +Space CRDT carries config ops controlling room inheritance: + +| Config | Behavior | +|--------|----------| +| `autoJoinRooms: true` | Space members auto-join all rooms; space key unlocks everything | +| `autoJoinRooms: false` | Rooms independent; space membership ≠ room access | + +Config ops are CRDT writes to the space topic. Clients process changes and update local join behavior. + +## Invite Model + +| Entity | Mechanism | Format | +|--------|-----------|--------| +| Space | Space key → unlocks all rooms (if autoJoin) | `gcs://invite/space/?key=` | +| Room | Room key → unlocks single room | `gcs://invite/room/?key=` | +| DM | Auto-generated room key | Implicit on creation | + +Room invites work standalone or reference a parent space. A user can be in a space without being in a private room within it. + +## Room Types (by auto-answer policy) Single unified model. Per-participant `autoAnswer` policy controls bot behavior: @@ -17,17 +47,12 @@ Single unified model. Per-participant `autoAnswer` policy controls bot behavior: | **None** | `@gcs` mention only | Multi-party human chat | | **GCS** | Every message | Q&A mode, solo chat | -## Pub/Sub Topics - -- **Room topic:** `ipfs-pubsub://gcs/chat/` -- **Lobby topic:** `ipfs-pubsub://gcs/chat/lobby` — public room registry (CRDT) - -Public rooms publish existence to lobby. Private rooms never publish to lobby. - ## Privacy App-layer encryption (not libp2p PSK). Per-room symmetric key encrypts message payloads before CRDT publish. Existence visible on pubsub network, content hidden. Key rotation deferred to post-MVP. +Public rooms publish existence to lobby. Private rooms never publish to lobby. + ## Message Flow 1. User sends message → Flutter Cubit → FFI → C++ GCS core @@ -38,13 +63,16 @@ App-layer encryption (not libp2p PSK). Per-room symmetric key encrypts message p ## MVP Scope -- Room creation (name → topic) +- Space creation (public → lobby, private → invite link) +- Room creation within space or standalone - Message send/receive with CRDT sync - GCS bot participant with mention/every-message auto-answer -- No encryption yet (public rooms only) +- Space/room invites via capability tokens +- No encryption yet (public spaces/rooms only) ## Deferred -- Private room key rotation +- Private room/space key rotation - Room archive / lifecycle management -- Room discovery beyond lobby (search, QR codes) +- Space discovery beyond lobby (search, QR codes) +- DM auto-creation on first message From 4b90fdd2a641c1ac558f4cc7a9b1a147641c2806 Mon Sep 17 00:00:00 2001 From: Super Genius Date: Sat, 15 Aug 2026 17:32:05 -0700 Subject: [PATCH 04/49] docs: add membership types and moderation model to GCS chat architecture --- .planning/notes/gcs-chat-architecture.md | 50 ++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/.planning/notes/gcs-chat-architecture.md b/.planning/notes/gcs-chat-architecture.md index ac708d5..1454e21 100644 --- a/.planning/notes/gcs-chat-architecture.md +++ b/.planning/notes/gcs-chat-architecture.md @@ -47,6 +47,56 @@ Single unified model. Per-participant `autoAnswer` policy controls bot behavior: | **None** | `@gcs` mention only | Multi-party human chat | | **GCS** | Every message | Q&A mode, solo chat | +## Membership Types + +| Level | Capabilities | Constraints | +|-------|-----------|-------------| +| **Super Admin** | Full control; cannot be demoted by other admins | Singular — creator only | +| **Admin** | Full control: config, membership, moderation | Multiple allowed | +| **Moderator** | Manage members, delete messages, pin content | Trusted participants | +| **Member** | Read, write, invite (if allowed) | Standard participant | +| **Guest** | Read-only | Temporary access | + +### Invite Permission + +Configurable per space/room: `membersCanInvite: true/false` + +### Destructive Action Rules + +| Condition | Required Approvals | +|-----------|------------------| +| Only 1 admin exists | 1 (that admin) | +| 2+ admins exist | Super Admin (creator) must approve | + +No timeouts — actions execute immediately once threshold met. + +### Membership CRDT Op + +``` +Op::Membership { + target: , + action: Add | Remove | ChangeRole, + role: SuperAdmin | Admin | Moderator | Member | Guest, + scope: Space | Room, + author: , + approvals: []?, // required for destructive when >1 admin + timestamp: +} +``` + +### Moderation CRDT Op + +``` +Op::Moderation { + action: DeleteMessage | PinMessage | KickUser | BanUser, + target: , + reason: , + author: +} +``` + +Moderation ops are tombstones — content preserved in CRDT, filtered from display. + ## Privacy App-layer encryption (not libp2p PSK). Per-room symmetric key encrypts message payloads before CRDT publish. Existence visible on pubsub network, content hidden. Key rotation deferred to post-MVP. From 7fd16da9bec70be3f28b008f34d94f803c8eeff9 Mon Sep 17 00:00:00 2001 From: Super Genius Date: Sat, 15 Aug 2026 17:37:04 -0700 Subject: [PATCH 05/49] =?UTF-8?q?docs:=20add=20extension=20model=20?= =?UTF-8?q?=E2=80=94=20genius-tube=20as=20superset=20of=20gcs-chat-core?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .planning/notes/gcs-chat-architecture.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.planning/notes/gcs-chat-architecture.md b/.planning/notes/gcs-chat-architecture.md index 1454e21..666909e 100644 --- a/.planning/notes/gcs-chat-architecture.md +++ b/.planning/notes/gcs-chat-architecture.md @@ -120,6 +120,26 @@ Public rooms publish existence to lobby. Private rooms never publish to lobby. - Space/room invites via capability tokens - No encryption yet (public spaces/rooms only) +## Extension Model + +The chat system is designed as a **core library** that can be embedded or extended: + +``` +gcs-chat-core/ ← this repo (src/lib/) + ├── CRDT sync engine (GlobalDB) + ├── Membership model (roles, permissions) + ├── Encryption layer (app-layer, per-room keys) + └── Pub/Sub topics (spaces, rooms, lobby) + +genius-tube/ ← separate repo (superset) + ├── Channel = Space + creator branding + monetization + ├── Stream/VOD = Room + video metadata ops + ├── Subscriber/Follower = Member + payment state + └── Uses gcs-chat-core as dependency (submodule or linked lib) +``` + +Genius-tube extends but does not modify core primitives. Channel ops (PublishStream, Monetization) are new CRDT types registered alongside core ops. + ## Deferred - Private room/space key rotation From 713295fc3dbfefb098eb05d24f369fa67ea1ba46 Mon Sep 17 00:00:00 2001 From: Super Genius Date: Sat, 15 Aug 2026 17:39:55 -0700 Subject: [PATCH 06/49] =?UTF-8?q?docs:=20start=20milestone=20v1.0=20GCS=20?= =?UTF-8?q?Chat=20=E2=80=94=20PROJECT.md,=20MILESTONES.md,=20REQUIREMENTS.?= =?UTF-8?q?md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .planning/workstreams/app/MILESTONES.md | 34 +++++++ .planning/workstreams/app/PROJECT.md | 93 ++++++++++++++++++ .planning/workstreams/app/REQUIREMENTS.md | 114 ++++++++++++++++++++++ .planning/workstreams/app/STATE.md | 33 +++++++ 4 files changed, 274 insertions(+) create mode 100644 .planning/workstreams/app/MILESTONES.md create mode 100644 .planning/workstreams/app/PROJECT.md create mode 100644 .planning/workstreams/app/REQUIREMENTS.md create mode 100644 .planning/workstreams/app/STATE.md diff --git a/.planning/workstreams/app/MILESTONES.md b/.planning/workstreams/app/MILESTONES.md new file mode 100644 index 0000000..fee4ea2 --- /dev/null +++ b/.planning/workstreams/app/MILESTONES.md @@ -0,0 +1,34 @@ +# Milestones: App Workstream + +## v1.0 GCS Chat (In Progress) + +**Started:** 2026-08-15 +**Status:** Planning + +**Goal:** Build a working multi-party chat with spaces, rooms, membership, and GCS bot integration. + +**Target features:** +- Space and room creation with configurable inheritance +- CRDT-based message sync +- Membership roles and moderation +- Invite system with capability tokens +- GCS bot participant with auto-answer policies + +--- + +## Future Milestones + +### v1.1 Private Spaces & Encryption +- App-layer symmetric key encryption +- Key rotation on membership change +- Encrypted DMs + +### v1.2 Rich Content +- File attachments +- Message threading +- Reactions + +### v2.0 Genius-Tube Integration +- Channels as branded spaces +- Stream/VOD rooms +- Subscriber/monetization layer diff --git a/.planning/workstreams/app/PROJECT.md b/.planning/workstreams/app/PROJECT.md new file mode 100644 index 0000000..90d37f1 --- /dev/null +++ b/.planning/workstreams/app/PROJECT.md @@ -0,0 +1,93 @@ +# Genius Cognitive System — App Workstream + +## What This Is + +Multi-party group chat application built on the Genius Cognitive System (GCS). Users create spaces and rooms, invite participants, and chat with both humans and AI specialists. The GCS system participates as a bot that can auto-answer questions or respond to mentions. + +## Core Value + +Users can create a space, invite others, and have a group conversation where an AI participant (GCS) responds to questions — all synchronized via CRDT without central servers. + +## Current Milestone: v1.0 GCS Chat + +**Goal:** Build a working multi-party chat with spaces, rooms, membership, and GCS bot integration. + +**Target features:** +- Space and room creation with configurable inheritance +- CRDT-based message sync (append-only, tombstone moderation) +- Membership roles (Super Admin, Admin, Moderator, Member, Guest) +- Invite system with capability tokens +- GCS bot participant with auto-answer policies +- Public lobby for space discovery + +## Requirements + +### Validated + +(None yet — ship to validate) + +### Active + +- [ ] **CHAT-01**: User can create a space (public or private) +- [ ] **CHAT-02**: User can create rooms within a space or standalone +- [ ] **CHAT-03**: User can invite others via capability tokens +- [ ] **CHAT-04**: User can send and receive messages in real-time +- [ ] **CHAT-05**: User can @mention the GCS bot for AI responses +- [ ] **CHAT-06**: Moderators can delete messages and manage members +- [ ] **CHAT-07**: Public spaces appear in lobby for discovery + +### Out of Scope + +| Feature | Reason | +|---------|--------| +| Video/audio streaming | Deferred to genius-tube extension | +| End-to-end encryption | MVP uses app-layer symmetric keys only | +| Message editing | Append-only CRDT for MVP | +| File attachments | Text-only for MVP | +| Mobile push notifications | Desktop-first | + +## Context + +- **Architecture:** See `.planning/notes/gcs-chat-architecture.md` +- **C++ Core:** `src/lib/gcs_core.cpp` (GCS integration, FFI to Flutter) +- **Flutter UI:** `src/app/` (Cubit state management, scaffold widgets) +- **FFI Bridge:** `src/ffi/` (Dart → C++ bindings) +- **Widget Library:** `src/app/scaffold/` (composite widgets, Jinja2 templates) +- **Extension:** genius-tube will consume this as `gcs-chat-core` dependency + +## Constraints + +- **Tech stack:** C++17 (no C++20 features), Flutter/Dart, libp2p GossipSub, GlobalDB CRDT +- **No central servers:** All state sync via CRDT over IPFS pubsub +- **Platform:** macOS first, then Linux, Windows, iOS, Android + +## Key Decisions + +| Decision | Rationale | Outcome | +|----------|-----------|---------| +| Single unified room model with `autoAnswer` policy | Simplifies state; Solo/Multi/Q&A are just config | — Pending | +| App-layer encryption (not libp2p PSK) | Per-room keys, easier rotation, existence visible | — Pending | +| Spaces as containers with `autoJoinRooms` config | Flexible: boundary or loose grouping | — Pending | +| Multiple Admins, no single Owner | Avoids "who owns this" problem, enables succession | — Pending | +| Super Admin = creator, cannot be demoted | Prevents rogue admin lockout | — Pending | +| Destructive actions need Super Admin approval when 2+ admins | Balance between agility and safety | — Pending | + +## Evolution + +This document evolves at phase transitions and milestone boundaries. + +**After each phase transition** (via `/gsd-transition`): +1. Requirements invalidated? → Move to Out of Scope with reason +2. Requirements validated? → Move to Validated with phase reference +3. New requirements emerged? → Add to Active +4. Decisions to log? → Add to Key Decisions +5. "What This Is" still accurate? → Update if drifted + +**After each milestone** (via `/gsd:complete-milestone`): +1. Full review of all sections +2. Core Value check — still the right priority? +3. Audit Out of Scope — reasons still valid? +4. Update Context with current state + +--- +*Last updated: 2026-08-15 after milestone v1.0 creation* diff --git a/.planning/workstreams/app/REQUIREMENTS.md b/.planning/workstreams/app/REQUIREMENTS.md new file mode 100644 index 0000000..d380286 --- /dev/null +++ b/.planning/workstreams/app/REQUIREMENTS.md @@ -0,0 +1,114 @@ +# Requirements: Genius Cognitive System — App Workstream + +**Defined:** 2026-08-15 +**Core Value:** Users can create a space, invite others, and have a group conversation where an AI participant (GCS) responds to questions — all synchronized via CRDT without central servers. + +## v1 Requirements + +Requirements for initial release. Each maps to roadmap phases. + +### Chat Core + +- [ ] **CORE-01**: User can create a space (public or private) +- [ ] **CORE-02**: User can create a room within a space or standalone +- [ ] **CORE-03**: User can configure space `autoJoinRooms` setting +- [ ] **CORE-04**: User can send and receive text messages in real-time +- [ ] **CORE-05**: Messages sync via CRDT across all room participants + +### Membership + +- [ ] **MEMB-01**: User can invite others via capability token (`gcs://invite/...`) +- [ ] **MEMB-02**: Space/room has roles: Super Admin, Admin, Moderator, Member, Guest +- [ ] **MEMB-03**: Super Admin (creator) cannot be demoted by other admins +- [ ] **MEMB-04**: Destructive actions require Super Admin approval when 2+ admins exist +- [ ] **MEMB-05**: Members can invite others if `membersCanInvite` is enabled + +### Moderation + +- [ ] **MODR-01**: Moderators can delete messages (tombstone op) +- [ ] **MODR-02**: Moderators can kick or ban users +- [ ] **MODR-03**: Deleted messages are preserved in CRDT but hidden from display + +### GCS Bot + +- [ ] **GCSB-01**: GCS bot joins rooms as a participant +- [ ] **GCSB-02**: Bot auto-answers every message when `autoAnswer: GCS` +- [ ] **GCSB-03**: Bot responds only to `@gcs` mentions when `autoAnswer: None` +- [ ] **GCSB-04**: Bot response routes through ELM specialist selection + +### Discovery + +- [ ] **DISC-01**: Public spaces publish existence to lobby topic +- [ ] **DISC-02**: Private spaces never appear in lobby +- [ ] **DISC-03**: User can browse public spaces in lobby + +## v2 Requirements + +Deferred to future release. Tracked but not in current roadmap. + +### Encryption + +- **ENCR-01**: Private spaces/rooms use app-layer symmetric key encryption +- **ENCR-02**: Key rotates when members leave +- **ENCR-03**: DMs auto-created with encryption on first message + +### Rich Content + +- **CONT-01**: User can attach files to messages +- **CONT-02**: User can reply in threads +- **CONT-03**: User can react to messages with emoji + +### Genius-Tube Integration + +- **GTUB-01**: Channel extends Space with creator branding +- **GTUB-02**: Stream/VOD extends Room with video metadata +- **GTUB-03**: Subscriber extends Member with payment state + +## Out of Scope + +Explicitly excluded. Documented to prevent scope creep. + +| Feature | Reason | +|---------|--------| +| Video/audio streaming | Deferred to genius-tube extension | +| End-to-end encryption | MVP uses app-layer symmetric keys only | +| Message editing | Append-only CRDT for MVP | +| File attachments | Text-only for MVP | +| Mobile push notifications | Desktop-first | +| Centralized user accounts | P2P identity via libp2p peer ID | + +## Traceability + +Which phases cover which requirements. Updated during roadmap creation. + +| Requirement | Phase | Status | +|-------------|-------|--------| +| CORE-01 | TBD | Pending | +| CORE-02 | TBD | Pending | +| CORE-03 | TBD | Pending | +| CORE-04 | TBD | Pending | +| CORE-05 | TBD | Pending | +| MEMB-01 | TBD | Pending | +| MEMB-02 | TBD | Pending | +| MEMB-03 | TBD | Pending | +| MEMB-04 | TBD | Pending | +| MEMB-05 | TBD | Pending | +| MODR-01 | TBD | Pending | +| MODR-02 | TBD | Pending | +| MODR-03 | TBD | Pending | +| GCSB-01 | TBD | Pending | +| GCSB-02 | TBD | Pending | +| GCSB-03 | TBD | Pending | +| GCSB-04 | TBD | Pending | +| DISC-01 | TBD | Pending | +| DISC-02 | TBD | Pending | +| DISC-03 | TBD | Pending | + +**Coverage:** +- v1 requirements: 20 total +- Mapped to phases: 0 +- Unmapped: 20 ⚠️ + +--- +*Requirements defined: 2026-08-15* +*Last updated: 2026-08-15 after milestone v1.0 creation* diff --git a/.planning/workstreams/app/STATE.md b/.planning/workstreams/app/STATE.md new file mode 100644 index 0000000..518ff2f --- /dev/null +++ b/.planning/workstreams/app/STATE.md @@ -0,0 +1,33 @@ +--- +gsd_state_version: 1.0 +milestone: v1.0 +milestone_name: GCS Chat +status: planning +last_updated: "2026-08-16T00:38:30.112Z" +last_activity: 2026-08-16 +progress: + total_phases: 0 + completed_phases: 0 + total_plans: 0 + completed_plans: 0 + percent: 0 +--- + +# Project State + +## Current Position + +Phase: Not started (defining requirements) +Plan: — +Status: Defining requirements +Last activity: 2026-08-16 — Milestone v1.0 started + +## Progress + +**Phases Complete:** 0 +**Current Plan:** N/A + +## Session Continuity + +**Stopped At:** N/A +**Resume File:** None From d68418053e0bd61ebb0178b1a223bc810ac9222b Mon Sep 17 00:00:00 2001 From: Super Genius Date: Sat, 15 Aug 2026 17:52:22 -0700 Subject: [PATCH 07/49] =?UTF-8?q?docs:=20create=20milestone=20v1.0=20roadm?= =?UTF-8?q?ap=20(7=20phases)=20=E2=80=94=20Phase=201=20includes=20cross-pl?= =?UTF-8?q?atform=20CI/CD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .planning/workstreams/app/REQUIREMENTS.md | 46 ++++---- .planning/workstreams/app/ROADMAP.md | 124 ++++++++++++++++++++++ .planning/workstreams/app/STATE.md | 78 +++++++++++--- 3 files changed, 213 insertions(+), 35 deletions(-) create mode 100644 .planning/workstreams/app/ROADMAP.md diff --git a/.planning/workstreams/app/REQUIREMENTS.md b/.planning/workstreams/app/REQUIREMENTS.md index d380286..cddadf9 100644 --- a/.planning/workstreams/app/REQUIREMENTS.md +++ b/.planning/workstreams/app/REQUIREMENTS.md @@ -83,32 +83,32 @@ Which phases cover which requirements. Updated during roadmap creation. | Requirement | Phase | Status | |-------------|-------|--------| -| CORE-01 | TBD | Pending | -| CORE-02 | TBD | Pending | -| CORE-03 | TBD | Pending | -| CORE-04 | TBD | Pending | -| CORE-05 | TBD | Pending | -| MEMB-01 | TBD | Pending | -| MEMB-02 | TBD | Pending | -| MEMB-03 | TBD | Pending | -| MEMB-04 | TBD | Pending | -| MEMB-05 | TBD | Pending | -| MODR-01 | TBD | Pending | -| MODR-02 | TBD | Pending | -| MODR-03 | TBD | Pending | -| GCSB-01 | TBD | Pending | -| GCSB-02 | TBD | Pending | -| GCSB-03 | TBD | Pending | -| GCSB-04 | TBD | Pending | -| DISC-01 | TBD | Pending | -| DISC-02 | TBD | Pending | -| DISC-03 | TBD | Pending | +| CORE-01 | Phase 2 | Pending | +| CORE-02 | Phase 2 | Pending | +| CORE-03 | Phase 2 | Pending | +| CORE-04 | Phase 3 | Pending | +| CORE-05 | Phase 1 | Pending | +| MEMB-01 | Phase 4 | Pending | +| MEMB-02 | Phase 4 | Pending | +| MEMB-03 | Phase 4 | Pending | +| MEMB-04 | Phase 4 | Pending | +| MEMB-05 | Phase 4 | Pending | +| MODR-01 | Phase 5 | Pending | +| MODR-02 | Phase 5 | Pending | +| MODR-03 | Phase 5 | Pending | +| GCSB-01 | Phase 6 | Pending | +| GCSB-02 | Phase 6 | Pending | +| GCSB-03 | Phase 6 | Pending | +| GCSB-04 | Phase 6 | Pending | +| DISC-01 | Phase 7 | Pending | +| DISC-02 | Phase 7 | Pending | +| DISC-03 | Phase 7 | Pending | **Coverage:** - v1 requirements: 20 total -- Mapped to phases: 0 -- Unmapped: 20 ⚠️ +- Mapped to phases: 20 +- Unmapped: 0 --- *Requirements defined: 2026-08-15* -*Last updated: 2026-08-15 after milestone v1.0 creation* +*Last updated: 2026-08-15 after roadmap creation* diff --git a/.planning/workstreams/app/ROADMAP.md b/.planning/workstreams/app/ROADMAP.md new file mode 100644 index 0000000..382ef0a --- /dev/null +++ b/.planning/workstreams/app/ROADMAP.md @@ -0,0 +1,124 @@ +# Roadmap: Genius Cognitive System — App Workstream (v1.0 GCS Chat) + +## Overview + +Build a working multi-party chat application where users create spaces and rooms, invite participants via capability tokens, chat in real-time via CRDT sync, and interact with the GCS bot as an AI participant — all without central servers. The roadmap progresses from C++ core foundation through entity management (spaces/rooms), messaging, membership, moderation, bot integration, and finally public discovery. + +## Phases + +**Phase Numbering:** +- Integer phases (1, 2, 3): Planned milestone work +- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED) + +Decimal phases appear between their surrounding integers in numeric order. + +- [ ] **Phase 1: Foundation** - C++ core scaffolding, GlobalDB CRDT integration, FFI bridge to Flutter, and cross-platform CI/CD +- [ ] **Phase 2: Spaces & Rooms** - Space and room creation with configurable inheritance +- [ ] **Phase 3: Messaging** - Real-time text messaging with CRDT sync across participants +- [ ] **Phase 4: Membership & Invites** - Roles, capability tokens, and permission model +- [ ] **Phase 5: Moderation** - Message tombstones, kick/ban, and admin approval flows +- [ ] **Phase 6: GCS Bot** - Bot participant with auto-answer policies and ELM routing +- [ ] **Phase 7: Discovery** - Public space lobby and private space isolation + +## Phase Details + +### Phase 1: Foundation +**Goal**: The C++ chat core compiles, links against GlobalDB, and exposes a working FFI surface that Flutter can call. +**Depends on**: Nothing (first phase) +**Requirements**: CORE-05 +**Success Criteria** (what must be TRUE): + 1. C++ core library builds on macOS, Linux, and Windows without errors + 2. GlobalDB CRDT instance can be initialized and a test op can be published/subscribed locally + 3. Flutter app can call into C++ via FFI and receive a response + 4. GossipSub topics can be created and joined from the C++ core + 5. CI/CD pipeline builds and tests on self-hosted runners for macOS, Linux, Windows +**Plans**: TBD + +### Phase 2: Spaces & Rooms +**Goal**: Users can create spaces and rooms with configurable inheritance, and the entity hierarchy is persisted via CRDT. +**Depends on**: Phase 1 +**Requirements**: CORE-01, CORE-02, CORE-03 +**Success Criteria** (what must be TRUE): + 1. User can create a public or private space and see it in their local space list + 2. User can create a room within a space or as a standalone room + 3. User can toggle `autoJoinRooms` on a space and observe the join behavior change + 4. Space/room metadata survives app restart (CRDT persistence) +**Plans**: TBD +**UI hint**: yes + +### Phase 3: Messaging +**Goal**: Users can send and receive text messages in real-time, with all room participants converging on the same message history via CRDT. +**Depends on**: Phase 2 +**Requirements**: CORE-04 +**Success Criteria** (what must be TRUE): + 1. User can send a text message to a room and see it appear locally + 2. A second user in the same room receives the message without manual refresh + 3. Message history is identical across all participants after sync + 4. Messages display in chronological order with sender identification +**Plans**: TBD +**UI hint**: yes + +### Phase 4: Membership & Invites +**Goal**: Users can invite others via capability tokens, and the five-tier role model (Super Admin → Guest) governs what actions each participant can take. +**Depends on**: Phase 3 +**Requirements**: MEMB-01, MEMB-02, MEMB-03, MEMB-04, MEMB-05 +**Success Criteria** (what must be TRUE): + 1. User can generate an invite link (`gcs://invite/...`) for a space or room + 2. Recipient can use the invite to join and appears in the member list + 3. Each participant has a visible role (Super Admin, Admin, Moderator, Member, Guest) + 4. Super Admin cannot be demoted by other admins + 5. When 2+ admins exist, destructive actions require Super Admin approval +**Plans**: TBD +**UI hint**: yes + +### Phase 5: Moderation +**Goal**: Moderators and admins can manage room content and membership through tombstone-based CRDT ops that preserve history while hiding removed content. +**Depends on**: Phase 4 +**Requirements**: MODR-01, MODR-02, MODR-03 +**Success Criteria** (what must be TRUE): + 1. Moderator can delete a message and it disappears from all participants' views + 2. Deleted message content is preserved in the CRDT (tombstone, not removal) + 3. Moderator can kick a user from a room; the user loses access immediately + 4. Moderator can ban a user; the ban persists across rejoin attempts +**Plans**: TBD +**UI hint**: yes + +### Phase 6: GCS Bot +**Goal**: The GCS system joins rooms as a participant and responds to messages according to the room's `autoAnswer` policy, routing requests through ELM specialist selection. +**Depends on**: Phase 3 +**Requirements**: GCSB-01, GCSB-02, GCSB-03, GCSB-04 +**Success Criteria** (what must be TRUE): + 1. GCS bot appears in the room participant list + 2. When `autoAnswer: GCS`, the bot responds to every message automatically + 3. When `autoAnswer: None`, the bot responds only when `@gcs` is mentioned + 4. Bot responses are routed through the correct ELM specialist for the query type + 5. Bot messages appear in the same CRDT message stream as human messages +**Plans**: TBD +**UI hint**: yes + +### Phase 7: Discovery +**Goal**: Users can browse public spaces in a global lobby, while private spaces remain invisible to the network. +**Depends on**: Phase 2 +**Requirements**: DISC-01, DISC-02, DISC-03 +**Success Criteria** (what must be TRUE): + 1. Creating a public space publishes its existence to the lobby topic + 2. Private spaces never appear in the lobby + 3. User can open the lobby and see a list of public spaces + 4. User can join a public space directly from the lobby +**Plans**: TBD +**UI hint**: yes + +## Progress + +**Execution Order:** +Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 + +| Phase | Plans Complete | Status | Completed | +|-------|----------------|--------|-----------| +| 1. Foundation | 0/TBD | Not started | - | +| 2. Spaces & Rooms | 0/TBD | Not started | - | +| 3. Messaging | 0/TBD | Not started | - | +| 4. Membership & Invites | 0/TBD | Not started | - | +| 5. Moderation | 0/TBD | Not started | - | +| 6. GCS Bot | 0/TBD | Not started | - | +| 7. Discovery | 0/TBD | Not started | - | diff --git a/.planning/workstreams/app/STATE.md b/.planning/workstreams/app/STATE.md index 518ff2f..ffc841b 100644 --- a/.planning/workstreams/app/STATE.md +++ b/.planning/workstreams/app/STATE.md @@ -3,10 +3,10 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: GCS Chat status: planning -last_updated: "2026-08-16T00:38:30.112Z" -last_activity: 2026-08-16 +last_updated: "2026-08-15T00:00:00.000Z" +last_activity: 2026-08-15 progress: - total_phases: 0 + total_phases: 7 completed_phases: 0 total_plans: 0 completed_plans: 0 @@ -15,19 +15,73 @@ progress: # Project State +## Project Reference + +See: .planning/workstreams/app/PROJECT.md (updated 2026-08-15) + +**Core value:** Users can create a space, invite others, and have a group conversation where an AI participant (GCS) responds to questions — all synchronized via CRDT without central servers. +**Current focus:** Phase 1 — Foundation + ## Current Position -Phase: Not started (defining requirements) -Plan: — -Status: Defining requirements -Last activity: 2026-08-16 — Milestone v1.0 started +Phase: 1 of 7 (Foundation) +Plan: 0 of TBD in current phase +Status: Ready to plan +Last activity: 2026-08-15 — Roadmap created for v1.0 GCS Chat + +Progress: [░░░░░░░░░░] 0% + +## Performance Metrics + +**Velocity:** +- Total plans completed: 0 +- Average duration: N/A +- Total execution time: 0 hours + +**By Phase:** + +| Phase | Plans | Total | Avg/Plan | +|-------|-------|-------|----------| +| - | - | - | - | + +**Recent Trend:** +- Last 5 plans: N/A +- Trend: N/A + +*Updated after each plan completion* + +## Accumulated Context + +### Decisions + +Decisions are logged in PROJECT.md Key Decisions table. +Recent decisions affecting current work: + +- [Roadmap]: Single unified room model with `autoAnswer` policy +- [Roadmap]: App-layer encryption (not libp2p PSK) — deferred to v1.1 +- [Roadmap]: Spaces as containers with `autoJoinRooms` config +- [Roadmap]: Multiple Admins, no single Owner +- [Roadmap]: Super Admin = creator, cannot be demoted +- [Roadmap]: Destructive actions need Super Admin approval when 2+ admins + +### Pending Todos + +None yet. + +### Blockers/Concerns + +- Phase 1 depends on GlobalDB CRDT integration from GNUS-NEO-SWARM Phase 3 — verify availability before planning. + +## Deferred Items -## Progress +Items acknowledged and carried forward from previous milestone close: -**Phases Complete:** 0 -**Current Plan:** N/A +| Category | Item | Status | Deferred At | +|----------|------|--------|-------------| +| *(none)* | | | | ## Session Continuity -**Stopped At:** N/A -**Resume File:** None +Last session: 2026-08-15 00:00 +Stopped at: Roadmap created for milestone v1.0 GCS Chat +Resume file: None From 40eb5f9a3dd34a8e0e012d1a0ea4e1ed27674242 Mon Sep 17 00:00:00 2001 From: Super Genius Date: Sat, 15 Aug 2026 17:52:44 -0700 Subject: [PATCH 08/49] docs: tag 2 pending todos with resolves_phase after v1.0 roadmap --- .planning/todos/pending/define-gcs-bot-identity-mapping.md | 1 + .planning/todos/pending/design-message-crdt-schema.md | 1 + 2 files changed, 2 insertions(+) diff --git a/.planning/todos/pending/define-gcs-bot-identity-mapping.md b/.planning/todos/pending/define-gcs-bot-identity-mapping.md index cf23ec8..b03d89f 100644 --- a/.planning/todos/pending/define-gcs-bot-identity-mapping.md +++ b/.planning/todos/pending/define-gcs-bot-identity-mapping.md @@ -3,6 +3,7 @@ title: Define GCS bot participant identity mapping date: 2026-08-15 priority: high context: gs-explore --ws app +resolves_phase: 6 --- # Define GCS bot participant identity mapping diff --git a/.planning/todos/pending/design-message-crdt-schema.md b/.planning/todos/pending/design-message-crdt-schema.md index 1a72e98..2449235 100644 --- a/.planning/todos/pending/design-message-crdt-schema.md +++ b/.planning/todos/pending/design-message-crdt-schema.md @@ -3,6 +3,7 @@ title: Design message CRDT schema for chat room ops date: 2026-08-15 priority: high context: gs-explore --ws app +resolves_phase: 3 --- # Design message CRDT schema From e2283b536571ce4dbe771a50ff7e60a2f0ec07d8 Mon Sep 17 00:00:00 2001 From: Super Genius Date: Sat, 15 Aug 2026 17:53:27 -0700 Subject: [PATCH 09/49] =?UTF-8?q?docs:=20fix=20Phase=201=20=E2=80=94=20all?= =?UTF-8?q?=205=20platforms=20(macOS,=20Linux,=20Windows,=20iOS,=20Android?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .planning/workstreams/app/ROADMAP.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.planning/workstreams/app/ROADMAP.md b/.planning/workstreams/app/ROADMAP.md index 382ef0a..26b3a95 100644 --- a/.planning/workstreams/app/ROADMAP.md +++ b/.planning/workstreams/app/ROADMAP.md @@ -12,7 +12,7 @@ Build a working multi-party chat application where users create spaces and rooms Decimal phases appear between their surrounding integers in numeric order. -- [ ] **Phase 1: Foundation** - C++ core scaffolding, GlobalDB CRDT integration, FFI bridge to Flutter, and cross-platform CI/CD +- [ ] **Phase 1: Foundation** - C++ core scaffolding, GlobalDB CRDT integration, FFI bridge to Flutter, and cross-platform CI/CD (macOS, Linux, Windows, iOS, Android) - [ ] **Phase 2: Spaces & Rooms** - Space and room creation with configurable inheritance - [ ] **Phase 3: Messaging** - Real-time text messaging with CRDT sync across participants - [ ] **Phase 4: Membership & Invites** - Roles, capability tokens, and permission model @@ -27,11 +27,11 @@ Decimal phases appear between their surrounding integers in numeric order. **Depends on**: Nothing (first phase) **Requirements**: CORE-05 **Success Criteria** (what must be TRUE): - 1. C++ core library builds on macOS, Linux, and Windows without errors + 1. C++ core library builds on macOS, Linux, Windows, iOS, and Android without errors 2. GlobalDB CRDT instance can be initialized and a test op can be published/subscribed locally 3. Flutter app can call into C++ via FFI and receive a response 4. GossipSub topics can be created and joined from the C++ core - 5. CI/CD pipeline builds and tests on self-hosted runners for macOS, Linux, Windows + 5. CI/CD pipeline builds and tests on self-hosted runners for macOS, Linux, Windows, iOS, Android **Plans**: TBD ### Phase 2: Spaces & Rooms From 7b80a5f49249a11cb02d1dd92ee10334859afa03 Mon Sep 17 00:00:00 2001 From: Super Genius Date: Sat, 15 Aug 2026 18:19:41 -0700 Subject: [PATCH 10/49] docs(01): capture phase context --- .../app/phases/01-foundation/01-CONTEXT.md | 106 ++++++++++++++++++ .../phases/01-foundation/01-DISCUSSION-LOG.md | 66 +++++++++++ 2 files changed, 172 insertions(+) create mode 100644 .planning/workstreams/app/phases/01-foundation/01-CONTEXT.md create mode 100644 .planning/workstreams/app/phases/01-foundation/01-DISCUSSION-LOG.md diff --git a/.planning/workstreams/app/phases/01-foundation/01-CONTEXT.md b/.planning/workstreams/app/phases/01-foundation/01-CONTEXT.md new file mode 100644 index 0000000..0d7ab28 --- /dev/null +++ b/.planning/workstreams/app/phases/01-foundation/01-CONTEXT.md @@ -0,0 +1,106 @@ +# Phase 1: Foundation - Context + +**Gathered:** 2026-08-15 +**Status:** Ready for planning + + +## Phase Boundary + +The C++ chat core compiles, links against GlobalDB (via GNUS-NEO-SWARM's `neoswarm_storage` / `sgns::crdt_globaldb`), exposes a working FFI surface that Flutter can call, builds on all 5 platforms (macOS, Linux, Windows, iOS, Android), and has CI/CD on self-hosted runners proving it. No chat features yet (spaces/rooms/messages are Phases 2–3) — this phase is the skeleton: build system, GlobalDB init + pub/sub smoke test, FFI bridge, CI. + + + + +## Implementation Decisions + +### FFI Surface Shape +- **D-01:** Single opaque-handle "core session" API in `src/ffi/` — one `extern "C"` header (`gcs_core.h` or similar) where everything funnels through a session handle (`gcs_init` → handle; subsequent calls take the handle). NOT per-module headers. Rationale: Cubit-style C++ state storage with a thin Flutter UI wrapper; one entry point keeps the C API small and the state ownership clearly in C++. + +### FFI Boundary / Library Layout +- **D-02:** Separate `gcs_ffi` **shared library** target (not folded into `gcs_core` static lib). `gcs_ffi` links `gcs_core` (and transitively `neoswarm_storage` → `sgns::crdt_globaldb` / `sgns::GeniusSDK_shared`). Flutter loads the shared lib (dylib/so/dll) directly. Mirrors the `neoswarm_ffi` plugin pattern but as a plain shared library the app links. +- **D-03:** `neoswarm_ffi` (GNUS-NEO-SWARM) may no longer be needed by the app once `gcs_ffi` exists — its SLM-facing functions get absorbed behind the GCS session API over time. Do NOT remove it in this phase; leave it working. Re-evaluate at Phase 6 (GCS Bot) when the bot path is wired through gcs_core. + +### Async / Event Model Across FFI +- **D-04:** C++ owns essentially all state and logic. Dart is a thin wrapper: input (send text, select channel, change config) and output (render what C++ gives it). +- **D-05:** Data arriving from the network (incoming messages, CRDT sync updates) is pushed to Dart via **native→Dart callbacks** — ffigen `Pointer` + `ReceivePort`/NativePort pattern. Dart does NOT poll. This is the same pattern `GNUS-NEO-SWARM/neoswarm_ffi` already uses — reuse that mechanism (listener registration on the session handle, e.g. `gcs_on_message(handle, dartPort)`-style). + +### CI/CD +- **D-06:** Follow the SuperGenius CI pattern (`.github/workflows/cmake.yml` in `../SuperGenius`): a `resolve-runners` job picks self-hosted runners by label (`sg-ubuntu-linux`, `sg-arm-linux`, `SG-WIN11`, `gv-OSX-Large`) with GitHub-hosted fallbacks, then a matrix job builds **Android (arm64-v8a, armeabi-v7a), iOS, OSX, Linux (x86_64, aarch64), Windows × Debug/Release**. +- **D-07:** Dependencies are **downloaded as prebuilt release tarballs** (thirdparty from `GeniusVentures/thirdparty` releases; additionally SuperGenius and GeniusSDK releases, tagged per `Target-ABI-branch-buildtype`) — NOT built per-run. GeniusSDK's workflow is the template for a consumer repo that downloads its deps this way. +- **D-08:** CI builds and ctests the **C++ core only** in Phase 1 (no Flutter app build in CI yet — desktop-first; Flutter smoke test of FFI is a local/dev-loop concern this phase). +- **D-09:** Self-hosted runner cleanup + container usage (`ghcr.io/geniusventures/debian-bullseye` for Linux/Android) copied from SuperGenius workflow; skip zkLLVM (GCS doesn't need it). + +### Claude's Discretion +- Exact C API function names/signatures in the session header (planner/researcher design; must stay C-only, opaque handle, C++17). +- GTest smoke-test specifics (GlobalDB local pub/sub round-trip, FFI init/echo call) — must use wait-condition templates, never sleep_for. +- Whether `gcs_ffi` lives under `src/ffi/` (CMake target there) vs `src/lib/` — user moved structure to `src/ffi` for "Dart FFI -> C usage", so `src/ffi/` is the expected home. + + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Architecture +- `.planning/notes/gcs-chat-architecture.md` — full chat architecture (spaces/rooms, topics `ipfs-pubsub://gcs/chat/`, CRDT ops, membership, GCS bot message flow Flutter Cubit → FFI → C++ GCS core → GossipSub → GlobalDB). Phase 1 builds the bottom of this stack. + +### CI/CD pattern to copy +- `../SuperGenius/.github/workflows/cmake.yml` — runner resolution, 5-platform matrix, prebuilt thirdparty release downloads, build/install/ctest, release upload. +- `../GeniusSDK/.github/workflows/cmake.yml` — consumer-repo variant (downloads SuperGenius + thirdparty releases); closest analog to what GCS CI must do. + +### Build system +- `CMakeLists.txt` (repo root) — parent build: cxx17 toolchain, find_package deps, `add_subdirectory(GNUS-NEO-SWARM)` + `add_subdirectory(src)`. +- `GNUS-NEO-SWARM/src/storage/CMakeLists.txt` — `neoswarm_storage` target, `sgns::crdt_globaldb` / `sgns::GeniusSDK_shared` linkage (hard-required pattern; FATAL_ERROR if missing), build-tree dylib redirect for GeniusSDK. + +### FFI pattern to reuse +- `GNUS-NEO-SWARM/neoswarm_ffi/ffigen.yaml` + `GNUS-NEO-SWARM/neoswarm_ffi/src/flutter_slm_bridge.c/.h` — working C-header → ffigen Dart bindings → callback pattern. + + + + +## Existing Code Insights + +### Reusable Assets +- `src/CMakeLists.txt` — `gcs_core` static library target already wired into the parent build (spdlog/fmt); Phase 1 extends this file (GlobalDB link via neoswarm_storage) and adds `src/ffi/` target. +- `src/lib/gcs_core.cpp` — placeholder anchor (`gcs::kGcsCoreVersion`); Phase 1 fleshes out init/shutdown around `GcsGlobalDb`. +- `GNUS-NEO-SWARM/src/storage/gcs_global_db.{hpp,cpp}` (`neoswarm_storage`) — GCS GlobalDB component from neoswarm Phase 3 Wave 1; the CRDT instance Phase 1 initializes and smoke-tests. +- `GNUS-NEO-SWARM/neoswarm_ffi/` — ffigen + NativePort callback machinery to mirror for `gcs_ffi`. +- `.github/workflows/deploy.yaml` — docs deploy only; build CI is net-new alongside it. + +### Established Patterns +- Conditional-compilation rule: missing required libraries fail at CMake configure time (`FATAL_ERROR`), never degrade to stub — apply to GlobalDB/GeniusSDK linkage in gcs targets. +- No OS `#ifdef` in C++ source; platform specifics live in `os//Platform.hpp` and CMake resolves platform via `CMAKE_SYSTEM_NAME`. +- C++17 ceiling; tests use wait-condition templates (no sleep_for). +- CMake layout convention from SuperGenius: `cmake -S build/ -B build//[/]` with `-DTHIRDPARTY_BUILD_DIR=...` — GCS already has `build/` directory skeleton (OSX/Linux/Windows/Android/iOS dirs present). + +### Integration Points +- Root `CMakeLists.txt` already does `add_subdirectory(GNUS-NEO-SWARM)` and `add_subdirectory(src)` — `gcs_ffi` slots into `src/` without touching the root file (only `src/CMakeLists.txt` + new `src/ffi/CMakeLists.txt`). +- Flutter app at `src/app/` (pubspec already has `ffi: ^2.2.0`); `src/app/pubspec.yaml` currently depends on `neoswarm_ffi` via path — will migrate to gcs bindings as they appear (not this phase). + + + + +## Specific Ideas + +- "Cubit style C++ state storage with a thin flutter UI wrapper" — the Bloc/Cubit state objects live in C++; Flutter only renders and forwards user input. +- "probably only send/receive FFI calls to dart... C++ handles most everything" + callbacks instead of polling for arriving data. +- CI/CD: "kick-off those builds on our self-hosted runners and they will pick-up thirdparty & SuperGenius & GeniusSDK and download dependencies" — realized as the SuperGenius/GeniusSDK release-download pattern (D-07). + + + + +## Deferred Ideas + +- Removing/retiring `neoswarm_ffi` in favor of `gcs_ffi` — re-evaluate at Phase 6 (GCS Bot) when inference routes through gcs_core. +- Flutter app builds in CI (iOS/Android app bundles) — later phase; Phase 1 CI covers C++ core only. +- zkLLVM in CI — not needed by GCS; skip. +- Message CRDT schema design — tracked in `.planning/todos/pending/design-message-crdt-schema.md`, resolves Phase 3. +- GCS bot identity mapping — tracked in `.planning/todos/pending/define-gcs-bot-identity-mapping.md`, resolves Phase 6. + + + +--- + +*Phase: 1-Foundation* +*Context gathered: 2026-08-15* diff --git a/.planning/workstreams/app/phases/01-foundation/01-DISCUSSION-LOG.md b/.planning/workstreams/app/phases/01-foundation/01-DISCUSSION-LOG.md new file mode 100644 index 0000000..7e9da91 --- /dev/null +++ b/.planning/workstreams/app/phases/01-foundation/01-DISCUSSION-LOG.md @@ -0,0 +1,66 @@ +# Phase 1: Foundation - Discussion Log + +> **Audit trail only.** Do not use as input to planning, research, or execution agents. +> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered. + +**Date:** 2026-08-15 +**Phase:** 1-Foundation +**Areas discussed:** FFI surface shape, FFI boundary / library layout, async/event model across FFI, CI/CD structure + +--- + +## FFI Surface Shape + +| Option | Description | Selected | +|--------|-------------|----------| +| Per-module `extern "C"` headers | One header per module (`gcs_chat.h`, `gcs_rooms.h`, ...) | | +| Single opaque-handle session API | One header; `gcs_init` → handle; all calls take the handle | ✓ | + +**User's choice:** "a single opaque-handle API" +**Notes:** Rationale given with area 2: Cubit-style C++ state storage with a thin Flutter UI wrapper. + +## FFI Boundary / Library Layout + +| Option | Description | Selected | +|--------|-------------|----------| +| Thin C wrapper inside `gcs_core` | Flutter links gcs_core static lib directly | | +| Separate `gcs_ffi` shared library | Own CMake target, links gcs_core; Flutter loads dylib/so/dll | ✓ | + +**User's choice:** "separate gcs_ffi shared library, not sure neoswarm_ffi is needed any more, but if so, that's ok" +**Notes:** neoswarm_ffi left working; removal re-evaluated at Phase 6. Recorded as D-03. + +## Async / Event Model Across FFI + +| Option | Description | Selected | +|--------|-------------|----------| +| Dart polls C++ | Dart calls receive/poll on timer or after send | | +| Native→Dart callbacks | C++ pushes events via ffigen NativeFunction + ReceivePort | ✓ | + +**User's choice:** "No, dart doesn't need to poll, but the point is that C++ handles most everything and Dart is a thin wrapper for input/output text or selecting channels, config, etc. But it of course can receive callbacks instead of polling for that data arriving." +**Notes:** Initial "send/receive FFI calls to dart" phrasing was clarified in follow-up — callbacks, not polling. Same pattern as neoswarm_ffi already uses. + +## CI/CD Structure + +| Option | Description | Selected | +|--------|-------------|----------| +| Monolithic matrix workflow | One workflow, 5-platform matrix, SuperGenius-style | ✓ | +| Per-platform workflows | Separate workflow files per platform | | +| Deps pre-installed on runners | Runner images carry thirdparty/SuperGenius/GeniusSDK | | +| Deps downloaded as prebuilt releases | gh release download from thirdparty/SuperGenius/GeniusSDK release tags | ✓ | + +**User's choice:** "Check how ../SuperGenius repo does the CI/CD" +**Notes:** SuperGenius `cmake.yml` inspected: resolve-runners job (self-hosted labels sg-ubuntu-linux / sg-arm-linux / SG-WIN11 / gv-OSX-Large with GitHub-hosted fallback), matrix Android(arm64-v8a, armeabi-v7a)/iOS/OSX/Linux(x86_64, aarch64)/Windows × Debug/Release, prebuilt thirdparty release tarballs, ctest, release upload. GeniusSDK workflow confirmed as the consumer-repo analog (downloads SuperGenius + thirdparty). GCS CI copies this, adds SuperGenius + GeniusSDK download steps, skips zkLLVM. Phase 1 CI builds C++ core only (no Flutter app build). + +## Claude's Discretion + +- Exact C API names/signatures (C-only, opaque handle, C++17) +- GTest smoke-test specifics (wait-condition templates, no sleep_for) +- `gcs_ffi` target placement under `src/ffi/` (expected home per user's repo restructure) + +## Deferred Ideas + +- Retiring `neoswarm_ffi` → re-evaluate Phase 6 (GCS Bot) +- Flutter app bundles in CI → later phase +- zkLLVM in CI — not needed by GCS +- Message CRDT schema → Phase 3 (todo tracked) +- GCS bot identity mapping → Phase 6 (todo tracked) From 54ed0bc5e16cd598a9248f5c26abf25bd5a81420 Mon Sep 17 00:00:00 2001 From: Super Genius Date: Sat, 15 Aug 2026 18:20:04 -0700 Subject: [PATCH 11/49] docs(state): record phase 1 context session --- .planning/workstreams/app/STATE.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.planning/workstreams/app/STATE.md b/.planning/workstreams/app/STATE.md index ffc841b..1b8347b 100644 --- a/.planning/workstreams/app/STATE.md +++ b/.planning/workstreams/app/STATE.md @@ -3,8 +3,9 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: GCS Chat status: planning -last_updated: "2026-08-15T00:00:00.000Z" -last_activity: 2026-08-15 +stopped_at: Phase 1 context gathered +last_updated: "2026-08-16T01:19:53.328Z" +last_activity: 2026-08-15 — Roadmap created for v1.0 GCS Chat progress: total_phases: 7 completed_phases: 0 @@ -34,6 +35,7 @@ Progress: [░░░░░░░░░░] 0% ## Performance Metrics **Velocity:** + - Total plans completed: 0 - Average duration: N/A - Total execution time: 0 hours @@ -45,6 +47,7 @@ Progress: [░░░░░░░░░░] 0% | - | - | - | - | **Recent Trend:** + - Last 5 plans: N/A - Trend: N/A @@ -82,6 +85,6 @@ Items acknowledged and carried forward from previous milestone close: ## Session Continuity -Last session: 2026-08-15 00:00 -Stopped at: Roadmap created for milestone v1.0 GCS Chat -Resume file: None +Last session: 2026-08-16T01:19:53.320Z +Stopped at: Phase 1 context gathered +Resume file: .planning/workstreams/app/phases/01-foundation/01-CONTEXT.md From 8b26e97f0775f38ed2662f488c9f4027f397beb2 Mon Sep 17 00:00:00 2001 From: Super Genius Date: Sat, 15 Aug 2026 19:06:00 -0700 Subject: [PATCH 12/49] docs(1): research phase 1 foundation domain --- .../app/phases/01-foundation/01-RESEARCH.md | 519 ++++++++++++++++++ 1 file changed, 519 insertions(+) create mode 100644 .planning/workstreams/app/phases/01-foundation/01-RESEARCH.md diff --git a/.planning/workstreams/app/phases/01-foundation/01-RESEARCH.md b/.planning/workstreams/app/phases/01-foundation/01-RESEARCH.md new file mode 100644 index 0000000..997ecfb --- /dev/null +++ b/.planning/workstreams/app/phases/01-foundation/01-RESEARCH.md @@ -0,0 +1,519 @@ +# Phase 1: Foundation - Research + +**Researched:** 2026-08-15 +**Domain:** C++ build system integration (cmaketemplate parent build), SuperGenius GlobalDB/CRDT, Dart FFI (ffigen + native→Dart callbacks), GitHub Actions CI with prebuilt release dependencies +**Confidence:** HIGH (all build/link/test claims verified against live files in this workspace; release tags verified via `gh release list`) + +## Summary + +Phase 1 is mostly a **wiring phase, not a design phase**: every hard problem (GlobalDB component, GeniusSDK linkage, ffigen pattern, CI release-download pattern) already has a working in-repo or sibling-repo implementation to copy. The `gcs_core` static library target already builds today (`build/OSX/Debug/gcs_src/libgcs_core.a` exists, configured via `cmake -S build/OSX -B build/OSX/Debug`), and `neoswarm_storage` already links `sgns::crdt_globaldb` + `sgns::GeniusSDK_shared` with a hard `FATAL_ERROR` contract. Phase 1's real work is: (1) link `gcs_core` to `neoswarm_storage`, (2) flesh out `gcs_core.cpp` around `GcsGlobalDb` lifecycle, (3) add a new `src/ffi/` `gcs_ffi` SHARED library with a single opaque-handle C API, (4) add a GTest smoke suite under a new GCS-root `test/` directory, and (5) author `.github/workflows/cmake.yml` copying the GeniusSDK consumer-repo workflow (which already downloads thirdparty + SuperGenius releases and builds 5 platforms). + +One notable gap discovered: **the Dart NativePort/ReceivePort callback pattern referenced in D-05 does NOT currently exist in `neoswarm_ffi`** — that plugin is the Flutter plugin template's stock `sum()`/`sum_long_running()` example with no `Dart_PostCObject`, no `Dart_Port`, and no `Pointer` callback anywhere in GNUS-NEO-SWARM, SuperGenius, or GeniusSDK. The working callback mechanism to mirror is therefore the well-known Dart `Dart_PostCObjectType` + `NativePort` API pattern (documented below with the exact ffigen idioms), not an existing in-repo implementation. This is flagged in Open Questions — the planner should treat D-05's "reuse that mechanism" as "implement the standard mechanism in the same style as neoswarm_ffi's ffigen setup", since only the ffigen scaffolding exists to reuse. + +**Primary recommendation:** Extend `src/CMakeLists.txt` to link `gcs_core PUBLIC neoswarm_storage`; create `src/ffi/CMakeLists.txt` with a `gcs_ffi` SHARED target (pattern from `GNUS-NEO-SWARM/neoswarm_ffi/src/CMakeLists.txt` + export-macro style from `genius_elm_chat_completions.h`); create GCS-root `test/` with a wait-condition smoke test modeled line-for-line on `GNUS-NEO-SWARM/test/storage/test_gcs_global_db.cpp` (injected-pubsub seam, port 0, no GeniusSDK node needed); write CI as a near-verbatim copy of `../GeniusSDK/.github/workflows/cmake.yml` minus zkLLVM. + +## User Constraints (from CONTEXT.md) + +### Locked Decisions +- **D-01:** Single opaque-handle "core session" API in `src/ffi/` — one `extern "C"` header where everything funnels through a session handle (`gcs_init` → handle; subsequent calls take the handle). NOT per-module headers. +- **D-02:** Separate `gcs_ffi` **shared library** target (not folded into `gcs_core` static lib). `gcs_ffi` links `gcs_core` (and transitively `neoswarm_storage` → `sgns::crdt_globaldb` / `sgns::GeniusSDK_shared`). Flutter loads the shared lib directly. +- **D-03:** `neoswarm_ffi` may no longer be needed by the app once `gcs_ffi` exists. Do NOT remove it in this phase; leave it working. Re-evaluate at Phase 6. +- **D-04:** C++ owns essentially all state and logic. Dart is a thin wrapper. +- **D-05:** Network data pushed to Dart via **native→Dart callbacks** — ffigen `Pointer` + `ReceivePort`/NativePort pattern. Dart does NOT poll. Listener registration on the session handle (e.g. `gcs_on_message(handle, dartPort)`-style). +- **D-06:** Follow SuperGenius CI pattern: `resolve-runners` job picks self-hosted runners by label (`sg-ubuntu-linux`, `sg-arm-linux`, `SG-WIN11`, `gv-OSX-Large`) with GitHub-hosted fallbacks, then a matrix job builds **Android (arm64-v8a, armeabi-v7a), iOS, OSX, Linux (x86_64, aarch64), Windows × Debug/Release**. +- **D-07:** Dependencies downloaded as **prebuilt release tarballs** (thirdparty from `GeniusVentures/thirdparty`; SuperGenius and GeniusSDK releases, tagged per `Target-ABI-branch-buildtype`) — NOT built per-run. GeniusSDK's workflow is the template. +- **D-08:** CI builds and ctests the **C++ core only** in Phase 1 (no Flutter app build in CI). +- **D-09:** Self-hosted runner cleanup + container usage (`ghcr.io/geniusventures/debian-bullseye` for Linux/Android) copied from SuperGenius workflow; skip zkLLVM. + +### Claude's Discretion +- Exact C API function names/signatures in the session header (C-only, opaque handle, C++17). +- GTest smoke-test specifics (GlobalDB local pub/sub round-trip, FFI init/echo call) — wait-condition templates, never sleep_for. +- Whether `gcs_ffi` lives under `src/ffi/` (expected home per user) vs `src/lib/`. + +### Deferred Ideas (OUT OF SCOPE) +- Removing/retiring `neoswarm_ffi` (Phase 6). +- Flutter app builds in CI (later phase). +- zkLLVM in CI. +- Message CRDT schema design (Phase 3). +- GCS bot identity mapping (Phase 6). + +## Phase Requirements + +| ID | Description | Research Support | +|----|-------------|------------------| +| CORE-05 | Messages sync via CRDT across all room participants | Phase 1 proves the substrate: `GcsGlobalDb` wraps `sgns::crdt::GlobalDB` (Put/Get/AddBroadcastTopic/AddListenTopic verified in SuperGenius `globaldb.hpp`); smoke test exercises init → `AddBroadcastTopic` + `AddListenTopic` on a test topic → `Put` → local CRDT store contains the value (Get round-trip). Real multi-participant sync semantics land in Phase 3 with the message schema; Phase 1 establishes the CRDT is initialized, started, and round-trips locally over a real GossipPubSub. | + +## Architectural Responsibility Map + +| Capability | Primary Tier | Secondary Tier | Rationale | +|------------|-------------|----------------|-----------| +| CRDT store lifecycle (init/start/shutdown) | C++ core (`gcs_core` via `GcsGlobalDb`) | — | `GcsGlobalDb` already owns this; gcs_core wraps it (D-04) | +| Pub/sub transport (GossipSub topics) | C++ core (SuperGenius `GossipPubSub` via GeniusSDK node or injected) | — | libp2p stack lives in C++; Dart never touches sockets | +| FFI symbol export / C ABI | `gcs_ffi` shared lib | — | D-01/D-02: thin translation layer over gcs_core, owns no state | +| Async event delivery to UI | `gcs_ffi` (Dart_PostCObject) | Dart `ReceivePort` | D-05: push not poll; C++ posts, Dart receives | +| State ownership (session, rooms later) | C++ core (opaque handle table in `gcs_ffi`) | — | D-01 opaque handle = pointer into C++-owned session map | +| Build orchestration per platform | `build//CMakeLists.txt` (cmaketemplate) | CI workflow | Existing skeleton; CI just drives `cmake -S build/ -B ...` | +| Dependency provisioning | Prebuilt release tarballs (CI) / sibling-repo build trees (local) | CMake auto-download fallback in cmaketemplate | D-07; cmaketemplate already auto-downloads when sibling dirs are absent | + +## Standard Stack + +### Core (all already in-tree — no new external packages this phase) +| Component | Version/Commit | Purpose | Why Standard | +|-----------|---------------|---------|--------------| +| `sgns::crdt_globaldb` (SuperGenius) | develop (release tags `OSX-develop-Debug` etc., latest 2026-08-14) [VERIFIED: `gh release list --repo GeniusVentures/SuperGenius`] | CRDT store: `Put`/`Get`/`AddBroadcastTopic`/`AddListenTopic`/`Start` [CITED: SuperGenius `src/crdt/globaldb/globaldb.hpp`] | The mandated GlobalDB; CONTEXT D-01..D-04 of neoswarm Phase 3 | +| `sgns::GeniusSDK_shared` (GeniusSDK) | develop (releases 2026-08-04) [VERIFIED: `gh release list --repo GeniusVentures/GeniusSDK`] | `GeniusSDKInit`/`GeniusSDKGetNode()` — in-process GeniusNode + shared GossipPubSub [CITED: GeniusSDK `src/GeniusSDK.h:200`, `src/GeniusSDK.hpp:22`] | D-15/D-16a: GcsGlobalDb pulls pubsub from the in-process SDK | +| `neoswarm_storage` (GNUS-NEO-SWARM submodule) | in-tree | `GcsGlobalDb` component (init/shutdown/IsRunning, injected-pubsub test seam) [CITED: `GNUS-NEO-SWARM/src/storage/gcs_global_db.hpp`] | Existing component Phase 1 initializes and smoke-tests | +| `sgns::ipfs_pubsub::GossipPubSub` (thirdparty/ipfs-pubsub) | in-tree thirdparty | `Start(port, ...)` → future, `Publish(topic, bytes)`, `Stop()` [CITED: `thirdparty/ipfs-pubsub/src/ipfs_pubsub/gossip_pubsub.hpp:110-146`] | Transport under GlobalDB; smoke test stands one up on port 0 | + +### Supporting +| Component | Purpose | When to Use | +|-----------|---------|-------------| +| GTest (thirdparty) | Test framework | All smoke tests; `find_package(GTest CONFIG REQUIRED)` already in root CMakeLists (guarded by `BUILD_TESTING`) | +| ffigen (Dart dev-dependency) | Generate Dart bindings from the C header | Local dev loop when the `gcs_ffi` header changes; config modeled on `neoswarm_ffi/ffigen.yaml` | +| Dart `ffi` package `^2.2.0` | `Pointer`, `NativePort` types | Already in `src/app/pubspec.yaml` [VERIFIED: read of pubspec] | +| `Dart_PostCObject` (Dart VM C API `dart_native_api.h`) | Native→Dart async message post | The D-05 callback mechanism; header ships with the Dart SDK / Flutter engine | + +### Alternatives Considered +| Instead of | Could Use | Tradeoff | +|------------|-----------|----------| +| Injected-pubsub smoke test (`GcsGlobalDb::Initialize(pubsub)`) | Full GeniusSDK init in tests (`GeniusSDKInit` → node online) | Full SDK init brings up the whole GeniusNode (wallet, network) — slow, needs ports/config, flaky in CI. The injected seam exists precisely for this (Tier 2 fixture pattern; CITED: gcs_global_db.hpp doc comment). Use full-SDK init only in the FFI-level test if planner wants an end-to-end init path — recommend deferring that to a manual/integration test, not the CI smoke suite. | +| `Dart_PostCObject` + ReceivePort | `NativeCallable.listener` (Dart 3.x isolate-callback style) | NativeCallable avoids dart_native_api.h but requires Dart-side isolate management and is newer/less documented in ffigen-generated code. D-05 names the ReceivePort/NativePort pattern explicitly — follow the decision. [ASSUMED — Dart version nuance; both work with `ffi: ^2.2.0`] | + +**Installation:** None. No new external packages are introduced this phase. All C++ deps come from prebuilt thirdparty/SuperGenius/GeniusSDK trees; Dart deps (`ffi`, `ffigen`) are already in `src/app/pubspec.yaml` / dev-dependencies pattern of `neoswarm_ffi`. + +## Package Legitimacy Audit + +| Package | Registry | Age | Downloads | Source Repo | slopcheck | Disposition | +|---------|----------|-----|-----------|-------------|-----------|-------------| +| *(none — this phase adds no external packages)* | — | — | — | — | — | — | + +**Packages removed due to slopcheck [SLOP] verdict:** none +**Packages flagged as suspicious [SUS]:** none + +Note: slopcheck was run and is available. The only Dart-side package this phase touches is the already-present `ffi: ^2.2.0` (Dart pub, not PyPI — slopcheck's pypi check is inapplicable). All C++ dependencies are prebuilt release artifacts from the three sibling GeniusVentures repos, not registry packages. + +## Build System Wiring (Q1, Q5, Q6 — verified against live files) + +### Entry points (Q6) + +The GCS repo has **two** CMake entry paths: + +1. **`build//CMakeLists.txt`** (the real one — cmaketemplate submodule). Chain: `build/OSX/CMakeLists.txt` → `build/CommonCompilerOptions.cmake` (platform/toolchain/thirdparty discovery, `get_default_root()` sets `PROJECT_ROOT` to the GCS repo root, `get_third_party_dir()` walks up to find `../thirdparty` with the `GeniusVentures/thirdparty` git remote) → `build/CommonBuildParameters.cmake` (find_package for all thirdparty deps, then `add_subdirectory(${PROJECT_ROOT}/GNUS-NEO-SWARM ...)` and `add_subdirectory(${PROJECT_ROOT}/src ${CMAKE_BINARY_DIR}/gcs_src)`). Platform dirs present: `build/{Android,iOS,Linux,OSX,Windows}/CMakeLists.txt` — all exist. [VERIFIED: directory listing + file reads] + +2. **Repo-root `CMakeLists.txt`** — a lighter top-level that also does `add_subdirectory(GNUS-NEO-SWARM)` + `add_subdirectory(src)`. Per `GNUS-NEO-SWARM/CMakeLists.txt` header comment, the standalone/canonical path is the `build/` chain; the root file exists for parent-style embedding. **The existing successful local build used path 1**: `build/OSX/Debug/CMakeCache.txt` shows `CMAKE_HOME_DIRECTORY=.../build/OSX`, generator Ninja, `CMAKE_BUILD_TYPE=Debug`, and `libgcs_core.a` present under `build/OSX/Debug/gcs_src/`. [VERIFIED: CMakeCache.txt] + +**Canonical local configure command (Q5):** +```bash +cmake -S build/OSX -B build/OSX/Debug -G Ninja -DCMAKE_BUILD_TYPE=Debug +cmake --build build/OSX/Debug -j +``` +(Linux: `build/Linux` + `ABI_SUBFOLDER_NAME` is auto-set from `CMAKE_SYSTEM_PROCESSOR` → `/x86_64` or `/aarch64`; Android: `build/Android` + `-DANDROID_ABI=arm64-v8a` + `-DCMAKE_ANDROID_NDK=...`; Windows: `build/Windows`; iOS: `build/iOS` + `-DPLATFORM=OS64`.) + +### Dependency env-var flow (Q1) + +Discovery order in `GNUS-NEO-SWARM/cmake/CommonBuildParameters.cmake` (lines 310–456) — all verified: + +| Variable | How set | Consumed by | +|----------|---------|-------------| +| `_THIRDPARTY_BUILD_DIR` | `build/CommonCompilerOptions.cmake` → `${THIRDPARTY_DIR}/build/${BUILD_PLATFORM_NAME}/${CMAKE_BUILD_TYPE}${ABI_SUBFOLDER_NAME}`; auto-downloads `GeniusVentures/thirdparty` release tarball if the sibling tree is missing | `cmake/CommonBuildParameters.cmake` (root) sets `THIRDPARTY_BUILD_DIR` from it; all `find_package` dirs derive from it | +| `GENIUSSDK_BUILD_DIR` (input) / `GENIUS_SDK_BUILD_DIR` (derived) | Pass `-DGENIUSSDK_BUILD_DIR=...` to override; else auto-detected as `${PROJECT_SUPER_ROOT}/GeniusSDK/build//[/]`; auto-downloads GeniusSDK release tarball via curl if `${PROJECT_SUPER_ROOT}/GeniusSDK` is absent | `find_package(GeniusSDK CONFIG)` at `${GENIUS_SDK_BUILD_DIR}/GeniusSDK/lib/cmake/GeniusSDK/` (fallback: the dir itself); `neoswarm_storage` include dirs | +| `SUPERGENIUS_BUILD_DIR` | Auto-detected as `${PROJECT_SUPER_ROOT}/SuperGenius/build//[/]` | `find_package(SuperGenius CONFIG)` at `${SUPERGENIUS_BUILD_DIR}/SuperGenius/lib/cmake/SuperGenius/`; include dirs `${SUPERGENIUS_BUILD_DIR}/SuperGenius/include[/evmrelay]` | +| `GENIUS_SDK_DIR` | Source tree path (`${PROJECT_SUPER_ROOT}/GeniusSDK`) — used for `GeniusSDK.hpp` include (not yet installed) | `neoswarm_storage` include dirs | +| `ZKLLVM_BUILD_DIR` | Auto-detected/auto-downloaded by CommonCompilerOptions; include dir added to `neoswarm_storage` only `if(DEFINED ...)` | Optional include path (GeniusNode.hpp transitively pulls ProofSystem headers) | + +Locally (this machine) the cache resolved to sibling build trees: `GENIUS_SDK_BUILD_DIR=../GeniusSDK/build/OSX/Debug`, `SUPERGENIUS_BUILD_DIR=../SuperGenius/build/OSX/Debug`. [VERIFIED: CMakeCache.txt] + +**CI flow (D-07):** the workflow downloads tarballs into sibling dirs *before* configure, so the auto-detect paths hit and no `-D` overrides are needed: +- `gh release download [-]-- --repo GeniusVentures/thirdparty -p .tar.gz` → extract to `thirdparty/` → `THIRDPARTY_BUILD_DIR=/thirdparty/build//[/]` +- Same pattern for `GeniusVentures/SuperGenius` (→ `SUPERGENIUS_DIR=/SuperGenius`) and — **new for GCS vs the GeniusSDK workflow** — `GeniusVentures/GeniusSDK` (→ `/GeniusSDK`), because GCS consumes GeniusSDK prebuilt rather than building it. +- GCS CI must download **three** dependency releases (thirdparty, SuperGenius, GeniusSDK), one more than the GeniusSDK workflow (which builds GeniusSDK itself). The GeniusSDK CMake auto-download fallback (curl in CommonBuildParameters.cmake lines 339–363) also works, but explicit `gh release download` steps matching D-07 are the intended path — deterministic and fail-fast. + +**Release tag naming convention (Q4):** `[-]--`, where ABI segment appears only for Android (`arm64-v8a`/`armeabi-v7a`) and Linux (`x86_64`/`aarch64`). Asset file name: `[-]-.tar.gz`. Verified live tags (2026-08-15): +- **SuperGenius**: all 12 matrix combos on develop (latest 2026-08-14), e.g. `OSX-develop-Debug`, `Linux-x86_64-develop-Release`, `Android-arm64-v8a-develop-Debug`, `Windows-develop-Release`, `iOS-develop-Debug`. [VERIFIED: `gh release list --repo GeniusVentures/SuperGenius`] +- **GeniusSDK**: all 12 combos on develop (2026-08-04). [VERIFIED: `gh release list --repo GeniusVentures/GeniusSDK`] +- **thirdparty**: all combos on develop (2026-07-24). [VERIFIED: `gh release list --repo GeniusVentures/thirdparty`] + +So every matrix cell GCS CI needs has a published prebuilt for all three deps today. Note: GeniusSDK develop releases are from **2026-08-04**, but `GeniusSDKGetNode()` was added in commit `d550800` (Aug 3) — the prebuilt should contain it; the `neoswarm_storage` build-tree-dylib redirect (CMakeLists lines 30–39) handles the case where the *install-prefix* copy lags. **Pitfall:** on CI there is no GeniusSDK build tree (only the extracted release), so the redirect is a no-op and the release's `libGeniusSDK_shared` must export `GeniusSDKGetNode` — worth a link-time sanity check on the first CI run (a failure surfaces as "Undefined symbols: GeniusSDKGetNode", which the neoswarm CMakeLists comment explicitly documents as the signal to re-run `ninja install` in GeniusSDK / refresh the release). + +### How gcs_core links GlobalDB (Q1) + +Minimal change to `src/CMakeLists.txt`: +```cmake +target_link_libraries(gcs_core PUBLIC + neoswarm_storage # brings sgns::crdt_globaldb + sgns::GeniusSDK_shared transitively (both PUBLIC on neoswarm_storage) + spdlog::spdlog + fmt::fmt +) +``` +`sgns::crdt_globaldb` and `sgns::GeniusSDK_shared` are `PUBLIC` links on `neoswarm_storage` [CITED: `GNUS-NEO-SWARM/src/storage/CMakeLists.txt:14-47`], so linking `neoswarm_storage` is sufficient. The include dirs for SuperGenius/ipfs-pubsub/libp2p/boost/wallet-core/GeniusSDK-src are also `PUBLIC` on `neoswarm_storage`, so `gcs_core` sources can `#include "storage/gcs_global_db.hpp"` and call its API without adding include paths. If `gcs_core.cpp` includes `gcs_global_db.hpp` (which forward-declares the heavy types — clean), no additional includes are needed; only if gcs_core touches `GossipPubSub`/`GlobalDB` headers directly would it need the thirdparty include dirs, which it inherits transitively anyway. + +`gcs_ffi` (new, `src/ffi/CMakeLists.txt`): +```cmake +add_library(gcs_ffi SHARED gcs_core_ffi.cpp) # name TBD — planner's discretion +target_link_libraries(gcs_ffi PRIVATE gcs_core) +target_compile_definitions(gcs_ffi PRIVATE GCS_FFI_EXPORTS) # dllexport macro, mirrors NEOSWARM_CHAT_C_EXPORTS +``` +Export-macro header pattern to copy verbatim: `GNUS-NEO-SWARM/src/genius_elm_chat_completions.h` lines 6–14 (`__declspec(dllexport/dllimport)` on `_WIN32`, empty elsewhere, `extern "C"` guard). Note this file uses `#if defined(_WIN32)` **in a header for export macros only** — that is the established in-repo idiom for FFI export headers and is distinct from the banned OS-ifdef-in-logic pattern. + +## GcsGlobalDb Smoke Test (Q2) + +`GcsGlobalDb::Initialize()` (no-arg) requires the full GeniusSDK init chain to have run (`GeniusSDKGetNode() != nullptr`), else `Error::SdkNotInitialized`. For CI smoke tests, use the **injected-pubsub overload** `Initialize(std::shared_ptr)` — the deliberate test seam. The complete, working recipe is `GNUS-NEO-SWARM/test/storage/test_gcs_global_db.cpp` (249 lines, in-tree, passing): [CITED: read of full file] + +1. `SetUpTestSuite`: configure soralog via `ConfiguratorFromYAML` (minimal console-sink YAML) + `libp2p::log::setLoggingSystem()` — **required**, otherwise `GossipPubSub` construction crashes with "Logging system is not ready". +2. Per-test temp dir under `std::filesystem::temp_directory_path()` with a unique salt; removed in `TearDown`. +3. Stand up pubsub: `sgns::crdt::KeyPairFileStorage keyStore(keyDir)` → `GetKeyPair()` → `std::make_shared(keyPair)` → `pubsub->Start(0, {}, "0.0.0.0", {}).get()` (port 0 = random free port). +4. `GcsGlobalDb::Config{ .m_dbPath = temp + "/db" }` → `Initialize(pubsub)` → expect success, `IsRunning() == true`. +5. Wait-condition template: `WaitForCondition(predicate, kWaitTimeout{25000ms})` — `condition_variable::wait_for(lock, slice)` polling loop with `kPollInterval{10ms}`, no `sleep_for`. Copy this template verbatim into the GCS test suite. +6. `Shutdown()` → `IsRunning() == false`; `pubsub->Stop()`. + +For the CORE-05 CRDT round-trip the smoke test adds (beyond the lifecycle test above): after init, use the underlying GlobalDB APIs — `AddBroadcastTopic("gcs-chat-smoke")` + `AddListenTopic("gcs-chat-smoke")` + `Put(HierarchicalKey, buffer)` → `Get(key)` returns the buffer — topic names follow the architecture convention `gcs/chat/`; for the smoke test a dedicated `gcs/chat/smoke-test` topic is appropriate. GlobalDB API verified: `Put` (with optional broadcast topic), `Get`, `AddBroadcastTopic`, `AddListenTopic`, `Start` [CITED: SuperGenius `src/crdt/globaldb/globaldb.hpp:89-225`]. `GcsGlobalDb` does not currently expose `Put/Get/AddBroadcastTopic` publicly (only lifecycle + IsRunning) — **the planner must decide**: either (a) add minimal pass-through accessors to `GcsGlobalDb` (small neoswarm change — requires care re: submodule PR scope), or (b) Phase 1's smoke test exercises CRDT through a `GossipPubSub::Publish` + topic membership only, deferring Put/Get to Phase 3. Option (a) is recommended — it makes CORE-05's substrate claim testable — but it touches the submodule, so the plan should sequence it as its own task with its own neoswarm-branch commit. + +Topic create/join (success criterion 4): GossipSub topics are implicit — "create/join" = `GlobalDB::AddBroadcastTopic` + `AddListenTopic`, or raw `GossipPubSub::Publish(topic, ...)`/subscription. No explicit join API exists. [CITED: gossip_pubsub.hpp / globaldb.hpp] + +## FFI Callback Mechanism (Q3) + +**What exists in `neoswarm_ffi` today** [VERIFIED: file reads + repo-wide grep]: +- `neoswarm_ffi/ffigen.yaml` — ffigen config: `dart run ffigen --config ffigen.yaml`, entry-point header `../../src/genius_slm_chat_c.h` (**note: that header does not exist in the tree** — `src/` has `genius_elm_chat_completions.h` instead; the yaml is stale), output `lib/genius_slm_bindings_generated.dart`, `comments: style: any, length: full`. +- `neoswarm_ffi/src/flutter_slm_bridge.{c,h}` — stock Flutter plugin template (`sum`, `sum_long_running`), `FFI_PLUGIN_EXPORT` from `os_defines.h`, `dart_shared_lib` define, Android 16k-page-size link option (`-Wl,-z,max-page-size=16384`). +- `neoswarm_ffi/src/CMakeLists.txt` — minimal Flutter-plugin CMake (3.10 floor, SHARED lib, PUBLIC_HEADER). +- C++ C-ABI example: `GNUS-NEO-SWARM/src/genius_elm_chat_completions.{h,cpp}` — the real pattern: `NEOSWARM_ELM_CHAT_C_API` export macro, `extern "C"`, `noexcept`, heap-string returns freed by a paired `...StringFree`, global-mutex thread safety, int status codes. **This is the header style `gcs_ffi`'s session header should mirror.** +- Test-side: `GNUS-NEO-SWARM/test/ffi/test_genius_elm_ffi.cpp` exercises the C API from GTest — the model for the gcs_ffi echo test. + +**What does NOT exist:** any use of `Dart_PostCObject`, `Dart_Port`, `ReceivePort`, `NativePort`, or `Pointer` callback registration — grep across GNUS-NEO-SWARM, SuperGenius, GeniusSDK, and `neoswarm_ffi/lib/*.dart` found zero hits. D-05's premise ("the same pattern neoswarm_ffi already uses") is inaccurate; only the ffigen scaffolding exists. + +**The standard mechanism to implement** (well-established Dart VM C API; [ASSUMED] from Dart documentation knowledge — not verifiable against in-repo code because no in-repo usage exists): + +C header (ffigen-compatible): +```c +#include +// Dart_PostCObjectType is provided by dart_native_api.h (ships with Dart SDK); +// declare the function-pointer type explicitly so ffigen sees it: +typedef void (*GcsMessageCallback)(int64_t port, const char* json); + +// Register the port: C++ stores it; when a message arrives, it posts a +// Dart_CObject (string) to that port via Dart_PostCObjectType. +GCS_FFI_API int gcs_on_message(GcsSession* handle, int64_t dart_port); +``` +Dart side: +```dart +final receivePort = ReceivePort(); +bindings.gcs_on_message(handle, receivePort.sendPort.nativePort); +receivePort.listen((msg) { /* msg is the posted string */ }); +``` +ffigen maps `int64_t` ↔ Dart `int`, function pointers ↔ `Pointer>`. The native library must link/include `dart_native_api.h` (`Dart_PostCObjectType Dart_PostCObject`); in a plain shared lib loaded by Flutter, the symbol resolves from the Flutter engine at load time — declare it via `#include "dart_native_api.h"` with the header copied or referenced from the Dart SDK, a common practice. [ASSUMED — needs one spike to confirm include strategy: copy `dart_native_api.h` + `dart_api_dl.c` into `src/ffi/` (the `package:ffigen`-documented approach) vs. weak-linkage declare. This is the one genuine implementation unknown in the phase; budget a small spike task.] + +## CI/CD (D-06..D-09) + +Copy `../GeniusSDK/.github/workflows/cmake.yml` (594 lines, read in full) with these deltas: + +1. **Checkout**: `actions/checkout` with `submodules: "recursive"` at workspace root (GCS has submodules: `GNUS-NEO-SWARM`, `build` (cmaketemplate), `gendoc-template`, `src/app/scaffold`). The GeniusSDK workflow checks out into a `GeniusSDK/` subdir; GCS should checkout to the default workspace root. +2. **Add a third download step**: GeniusSDK release (tag `[-]--`, asset `[-]-.tar.gz`) into `${{github.workspace}}/GeniusSDK`. thirdparty + SuperGenius steps copy verbatim. +3. **Delete the zkLLVM download step** (D-09). Caveat: `CommonCompilerOptions.cmake` auto-downloads zkLLVM if absent (curl from `GeniusVentures/zkLLVM` releases) — it is not skippable via a flag today because `GeniusNode.hpp` transitively includes ProofSystem headers. GCS CI will still pull the zkLLVM *headers* release implicitly at configure time unless the planner adds a `-DZKLLVM_BUILD_DIR=` pointing somewhere inert or a skip flag. Cheapest correct move: let the existing auto-download run (it's header-only consumption for GCS) and simply don't add the explicit step; D-09's "skip zkLLVM" then means "no explicit step, no assigner binaries" rather than "zero zkLLVM bytes". Flag for planner/user confirmation — see Open Questions. +4. **Layout expectation**: the auto-detect requires the three dep trees as *siblings of the GCS checkout* (`PROJECT_SUPER_ROOT` = parent of the found `thirdparty` = `get_third_party_dir` walks up from `build/cmake` looking for a dir containing `thirdparty` with the right git remote). The GeniusSDK workflow extracts deps *inside* the workspace (`${{github.workspace}}/thirdparty`, `.../SuperGenius`, `.../GeniusSDK`) and passes `-DTHIRDPARTY_BUILD_DIR` / `-DSUPERGENIUS_DIR` explicitly. For GCS, the workspace IS the repo root, so extracted `thirdparty/` at workspace root is found by `get_third_party_dir` **only if it has a `.git` with the GeniusVentures/thirdparty remote** — a tarball extraction has no `.git`. **Therefore GCS CI must pass `-DTHIRDPARTY_DIR=/thirdparty` explicitly** (input to `get_third_party_dir`'s `THIRDPARTY_DIR` early-out at CommonCompilerOptions.cmake:91) — then `PROJECT_SUPER_ROOT` = workspace and SuperGenius/GeniusSDK auto-detect from sibling dirs `/SuperGenius`, `/GeniusSDK` resolves cleanly. This is the one non-copy-paste wiring detail; the GeniusSDK workflow's explicit `-DTHIRDPARTY_BUILD_DIR=...` achieves the same end (bypasses discovery). Either works; passing `-DTHIRDPARTY_BUILD_DIR` (full build path, as GeniusSDK CI does) is the more proven option. +5. **ctest**: copy the SuperGenius test steps (lines 703–740): ctest on Linux (inside `dbus-run-session` + gnome-keyring stub — needed because GeniusSDK wallet touches the keyring; GCS's smoke tests don't init the SDK, so plain `ctest` may suffice, but copying the wrapper is harmless), OSX, Windows Release. Android/iOS are cross-compiles — **no ctest there** (matches SuperGenius: tests only run on Linux/OSX/Windows). +6. **Runner labels** (D-06): self-hosted `sg-ubuntu-linux` (Linux x86_64 + Android builds, container `ghcr.io/geniusventures/debian-bullseye:latest`), `sg-arm-linux` (Linux aarch64), `SG-WIN11`, `gv-OSX-Large` (OSX + iOS); fallbacks `ubuntu-latest`, `ubuntu-24.04-arm`, `windows-2022`, `macos-latest`. Copy the `resolve-runners` job verbatim (uses `secrets.GNUS_TOKEN_1`, `gh api` repo+org runner merge). +7. **Self-hosted cleanup** (D-09): copy the "Clean workspace" step verbatim (git clean/reset + `rm -rf thirdparty zkLLVM SuperGenius GeniusSDK`). +8. **Release upload**: optional this phase. The GeniusSDK workflow creates `[-]--` releases of its own build output — GCS will eventually need this (so the Flutter app and downstream repos can pull `gcs_ffi` binaries), and adding it now is ~30 lines copied verbatim. Recommend including (it also smoke-proves artifact packaging), but it's planner's discretion. +9. Trigger branches: `develop` + `main`, paths-ignore `.github/**`, plus `workflow_dispatch`. + +## Architecture Patterns + +### Recommended Project Structure (additions this phase) +``` +src/ +├── lib/ +│ └── gcs_core.cpp # fleshed out: session state owning GcsGlobalDb +├── ffi/ +│ ├── CMakeLists.txt # NEW — gcs_ffi SHARED target +│ ├── gcs_core.h # NEW — single opaque-handle C API (D-01) +│ └── gcs_core_ffi.cpp # NEW — handle table + thunk to gcs_core +├── app/ # Flutter app (untouched this phase except eventual binding regen) +test/ +├── CMakeLists.txt # NEW — GCS-root test tree (BUILD_TESTING-gated; root CMakeLists already has the add_subdirectory hook via build/CommonBuildParameters.cmake:146-151) +├── test_wait_condition.hpp # NEW — WaitForCondition template copied from neoswarm test +└── test_gcs_core_smoke.cpp # NEW — GlobalDB lifecycle + CRDT round-trip + FFI init/echo +``` + +### Pattern 1: Opaque-handle C session API +**What:** All FFI calls take a `GcsSession*` handle from `gcs_init()`; C++ keeps a mutex-guarded handle→object map (or returns a raw pointer directly — simpler, standard for this shape; neoswarm's ELM API uses a global instance + mutex instead). +**When to use:** D-01 mandates it. +**Example (style copied from `genius_elm_chat_completions.h`):** +```c +// Source: GNUS-NEO-SWARM/src/genius_elm_chat_completions.h (export-macro + extern "C" idiom) +#if defined( _WIN32 ) +#if defined( GCS_FFI_EXPORTS ) +#define GCS_FFI_API __declspec( dllexport ) +#else +#define GCS_FFI_API __declspec( dllimport ) +#endif +#else +#define GCS_FFI_API +#endif + +#if defined( __cplusplus ) +extern "C" +{ +#endif + typedef struct GcsSession GcsSession; // opaque + GCS_FFI_API GcsSession* gcs_init( const char* db_path ) /* noexcept in C++ */; + GCS_FFI_API int gcs_join_topic( GcsSession* session, const char* topic ); + GCS_FFI_API int gcs_publish( GcsSession* session, const char* topic, const char* utf8_payload ); + GCS_FFI_API int gcs_on_message( GcsSession* session, int64_t dart_port ); + GCS_FFI_API void gcs_shutdown( GcsSession* session ); +#if defined( __cplusplus ) +} +#endif +``` + +### Pattern 2: Injected-pubsub test seam (Tier 2 fixture) +**What:** `GcsGlobalDb::Initialize(pubsub)` overload lets tests run the full init chain against a real `GossipPubSub` on port 0 without bringing GeniusNode online. +**Source:** `GNUS-NEO-SWARM/test/storage/test_gcs_global_db.cpp` (verbatim recipe in "GcsGlobalDb Smoke Test" above). + +### Pattern 3: Hard-required dependency linkage (no stubs) +**What:** `if(TARGET sgns::crdt_globaldb) ... else() message(FATAL_ERROR ...)`. +**Source:** `GNUS-NEO-SWARM/src/storage/CMakeLists.txt:14-18`. Apply the same to `gcs_core` → `neoswarm_storage` linkage: if the target is missing, fail configure. + +### Anti-Patterns to Avoid +- **Polling from Dart** for incoming messages — D-05 forbids it; use the port-post callback. +- **Folding the FFI symbols into `gcs_core`** — D-02 requires a separate SHARED lib; static lib + Flutter dynamic loading don't mix on iOS/Android the way the plugin flow expects. +- **`sleep_for` in tests** — banned project-wide; copy the WaitForCondition template. +- **Building deps in CI** — D-07; any CI job compiling thirdparty/SuperGenius/GeniusSDK from source is a regression. +- **Per-module C headers** — D-01; one session header. +- **OS `#ifdef` in C++ logic** — banned (exception: the `_WIN32` dllexport macro block in FFI headers is the established idiom). + +## Don't Hand-Roll + +| Problem | Don't Build | Use Instead | Why | +|---------|-------------|-------------|-----| +| CRDT store | Custom op log / vector-clock sync | `sgns::crdt::GlobalDB` via `GcsGlobalDb` | CRDT correctness (convergence, tombstones, DAG sync) is a research-grade problem; the component exists and is tested | +| Pub/sub transport | Raw libp2p gossipsub wiring | `GossipPubSub` (thirdparty/ipfs-pubsub) | Already integrated with SuperGenius keypair/identity | +| Wait-for-async in tests | `sleep_for` loops | `WaitForCondition` cv-polling template (copy from `test_gcs_global_db.cpp`) | Project rule; deterministic, fast-failing | +| Dart↔native string memory | `malloc` without paired free | `...StringFree` pattern (mirrors `GeniusElmStringFree`) | Cross-allocator heap mismatch crashes on Windows | +| Dependency acquisition in CI | `git clone` + build | `gh release download` of prebuilt tarballs | D-07; SuperGenius build is ~hours, download is ~minutes | +| Runner selection | Hardcoded `runs-on` | `resolve-runners` job (copy verbatim) | Self-hosted with hosted fallback, org+repo runner merge | + +**Key insight:** every deceptively complex subsystem in this phase (CRDT, gossip, FFI codegen, CI runner orchestration) already has an in-repo or sibling-repo implementation. The phase's risk is **wiring mistakes** (wrong `-D` vars, wrong include paths, wrong link order), not algorithmic risk. + +## Common Pitfalls + +### Pitfall 1: Stale GeniusSDK install-prefix dylib +**What goes wrong:** Link fails with `Undefined symbols: GeniusSDKGetNode` because the imported `sgns::GeniusSDK_shared` points at the install prefix, which lags the build tree. +**Why it happens:** GeniusSDK's install step exports the C header + lib but `GeniusSDK.hpp` (C++ API) isn't installed; the dylib in the prefix can be older than the build tree. +**How to avoid:** `neoswarm_storage` CMakeLists already redirects `IMPORTED_LOCATION` to the build-tree dylib when present (lines 30–39). Locally: re-run `ninja install` in GeniusSDK if you see the undefined-symbol error. In CI: the extracted release has no build tree, so the release itself must be fresh enough (develop releases from 2026-08-04 postdate the Aug 3 commit — OK today, but re-verify if releases go stale). +**Warning signs:** "Undefined symbols: GeniusSDKGetNode" at link time. + +### Pitfall 2: soralog logging system not configured in tests +**What goes wrong:** `GossipPubSub` construction crashes with "Logging system is not ready". +**Why:** SuperGenius `base::createLogger` asserts a configured soralog `LoggingSystem`. +**How to avoid:** Copy the `SetUpTestSuite` soralog YAML block from `test_gcs_global_db.cpp` verbatim. +**Warning signs:** Segfault/assert on first pubsub construction in a new test binary. + +### Pitfall 3: `get_third_party_dir` remote check fails on CI tarballs +**What goes wrong:** CMake configure can't find thirdparty on CI even though it was downloaded and extracted to the workspace. +**Why:** `get_third_party_dir()` requires `/.git` with a `GeniusVentures/thirdparty` remote; `gh release download` + `tar xzf` produces no `.git`. +**How to avoid:** Pass `-DTHIRDPARTY_BUILD_DIR=/thirdparty/build//[/]` explicitly (GeniusSDK CI pattern), which bypasses discovery via the `_THIRDPARTY_BUILD_DIR` cache path. +**Warning signs:** "Cannot find thirdparty directory required to build" then an attempted (failing or slow) auto-download in CI logs. + +### Pitfall 4: ffigen entry-point header drift +**What goes wrong:** `dart run ffigen --config ffigen.yaml` fails or generates stale bindings. +**Why:** `neoswarm_ffi/ffigen.yaml` references `../../src/genius_slm_chat_c.h`, which does not exist (renamed/replaced by `genius_elm_chat_completions.h`). The yaml was never updated. +**How to avoid:** GCS's `ffigen.yaml` must point at the real `src/ffi/gcs_core.h`; add binding regeneration as an explicit dev-loop step in the plan (not CI this phase). +**Warning signs:** ffigen "file not found" or Dart bindings missing new functions. + +### Pitfall 5: Dual entry-point confusion (root CMakeLists vs build/) +**What goes wrong:** Configuring from the repo root (`cmake -S . -B out`) takes the root CMakeLists path, which skips `CommonCompilerOptions.cmake` — no thirdparty discovery, no `PROJECT_SUPER_ROOT`, and the GNUS-NEO-SWARM subdirectory then can't resolve `_THIRDPARTY_BUILD_DIR`. +**Why:** Two entry points exist; the root one is for parent-embedding scenarios where the parent already resolved deps. +**How to avoid:** Always configure via `cmake -S build/ -B build//`. CI and docs should only ever show this form. +**Warning signs:** `find_package` failures for OpenSSL/Boost/etc. immediately after configure starts. + +### Pitfall 6: Dart callback posted after isolate/handle death +**What goes wrong:** Crash or leaked posts when the Dart side disposes the ReceivePort while C++ still holds the port. +**Why:** `Dart_PostCObject` on a closed port is safe (returns false), but C++ must also stop posting when the session shuts down, and `gcs_shutdown` must unregister before destroying state. +**How to avoid:** Session handle owns the registered port; `gcs_shutdown` clears it under the session mutex before tearing down `GcsGlobalDb`; the io thread (owned by `GcsGlobalDb`) must be joined (it is — `Shutdown()` joins) before the handle is freed. +**Warning signs:** Crash in `Dart_PostCObject` on shutdown path. [ASSUMED — standard Dart API_DL behavior; will be validated by the FFI smoke test's shutdown ordering] + +## Code Examples + +### Wait-condition template (copy verbatim) +```cpp +// Source: GNUS-NEO-SWARM/test/storage/test_gcs_global_db.cpp:72-90 +bool WaitForCondition( const std::function &predicate, std::chrono::milliseconds timeout ) +{ + std::mutex mtx; + std::condition_variable cv; + std::unique_lock lock( mtx ); + const auto deadline = std::chrono::steady_clock::now() + timeout; + while ( std::chrono::steady_clock::now() < deadline ) + { + if ( predicate() ) + { + return true; + } + const auto remaining = std::chrono::duration_cast( + deadline - std::chrono::steady_clock::now() ); + const auto slice = std::min( kPollInterval, remaining ); + cv.wait_for( lock, slice ); + } + return predicate(); +} +``` + +### GossipPubSub bring-up (test fixture) +```cpp +// Source: GNUS-NEO-SWARM/test/storage/test_gcs_global_db.cpp:140-158 +sgns::crdt::KeyPairFileStorage keyStore( keyDir ); +auto keyPairResult = keyStore.GetKeyPair(); +auto pubsub = std::make_shared( keyPairResult.value() ); +auto startError = pubsub->Start( 0, {}, "0.0.0.0", {} ).get(); // port 0 = ephemeral +``` + +### gcs_core link change +```cmake +# Source: pattern from GNUS-NEO-SWARM/src/storage/CMakeLists.txt +if(TARGET neoswarm_storage) + target_link_libraries(gcs_core PUBLIC neoswarm_storage) +else() + message(FATAL_ERROR "neoswarm_storage target not found — GNUS-NEO-SWARM must be configured before gcs_core") +endif() +``` + +## State of the Art + +| Old Approach | Current Approach | When Changed | Impact | +|--------------|------------------|--------------|--------| +| Build deps from source in CI | Prebuilt release tarballs (`gh release download`, tag `Target-ABI-branch-buildtype`) | Established across SuperGenius/GeniusSDK CI (current) | CI time drops from hours to minutes; GCS copies it | +| Flutter plugin template (`flutter create --template=plugin`) for FFI | Plain SHARED lib target loaded via `DynamicLibrary.open` + ffigen | neoswarm_ffi still uses plugin template; gcs_ffi per D-02 is a plain shared lib | Simpler CMake (no Flutter toolchain coupling in C++ build) | +| Dart polling loop for native events | NativePort push (`Dart_PostCObject`) | D-05 (this phase) | No in-repo precedent — see Open Questions | + +**Deprecated/outdated:** +- `neoswarm_ffi/ffigen.yaml` entry point (`genius_slm_chat_c.h`) — stale; file doesn't exist. Do not copy that path. + +## Assumptions Log + +| # | Claim | Section | Risk if Wrong | +|---|-------|---------|---------------| +| A1 | Dart NativePort mechanism details (`Dart_PostCObject`, `sendPort.nativePort`, ffigen `int64_t` mapping) follow the standard documented Dart API_DL pattern | FFI Callback Mechanism | Medium — no in-repo precedent exists to copy; a small spike task in the plan de-risks it. If the include/link strategy for `dart_native_api.h` differs, only `src/ffi/` files are affected. | +| A2 | `NativeCallable.listener` is a viable alternative but ReceivePort is preferred per D-05 | Alternatives Considered | Low — D-05 already locked the pattern. | +| A3 | GCS CI can rely on GeniusSDK develop releases (2026-08-04) containing `GeniusSDKGetNode` (added Aug 3) | Build System Wiring | Low-Medium — dates line up, but the first CI run is the real verification; failure mode is a clear link error with a documented fix (fresher GeniusSDK release). | +| A4 | Plain `ctest` suffices for GCS smoke tests on Linux (no gnome-keyring wrapper needed) because tests use the injected-pubsub seam and never init GeniusSDK | CI/CD item 5 | Low — copying the dbus-run-session wrapper verbatim is zero-cost insurance if wrong. | +| A5 | Letting cmaketemplate's zkLLVM auto-download run in CI (headers only) satisfies D-09's "skip zkLLVM" intent | CI/CD item 3 | Low — worst case CI downloads a tarball it doesn't compile against; confirm with user if "skip" meant "zero zkLLVM artifacts". | + +## Open Questions + +1. **D-05 references a neoswarm_ffi callback mechanism that doesn't exist.** + - What we know: neoswarm_ffi is the stock Flutter plugin template; zero `Dart_PostCObject`/`NativePort` usage anywhere in the workspace's C++/Dart code. + - What's unclear: Whether the user has seen this pattern in some other repo (GeniusWallet? a branch not fetched?) that should be the reference instead of the standard Dart API_DL idiom. + - Recommendation: Plan a small spike task ("stand up minimal NativePort echo through gcs_ffi locally") before the full FFI task; implement the standard mechanism; flag to user at plan review. + +2. **CRDT round-trip needs GcsGlobalDb accessors (Put/Get/AddBroadcastTopic) that don't exist yet.** + - What we know: `GcsGlobalDb` public API is lifecycle-only (Initialize/Shutdown/IsRunning); `m_db` is private. + - What's unclear: Whether Phase 1 may extend `GcsGlobalDb` in the GNUS-NEO-SWARM submodule (separate branch/PR there) or whether CORE-05's smoke proof should use raw `GossipPubSub::Publish` + GlobalDB accessed some other way. + - Recommendation: Planner sequences a submodule task adding minimal read-only accessors (or pass-throughs) to `GcsGlobalDb`, committed on a neoswarm branch, before the GCS smoke test task. Confirm scope with user since STATE.md lists "Phase 1 depends on GlobalDB CRDT integration from GNUS-NEO-SWARM Phase 3" as a concern. + +3. **zkLLVM in CI: header-only consumption vs. full skip.** + - What we know: `CommonCompilerOptions.cmake` auto-downloads zkLLVM when absent; `neoswarm_storage` adds its include dir conditionally; D-09 says skip zkLLVM. + - What's unclear: Whether "skip" means "no explicit CI step" (auto-download still runs, headers only) or "zero zkLLVM presence" (requires a CMake skip flag that doesn't exist yet). + - Recommendation: Default to no-explicit-step (option 1); if the user wants zero-presence, planner adds a `GCS_SKIP_ZKLLVM`-style guard to CommonCompilerOptions — small but touches the cmaketemplate submodule. + +## Environment Availability + +| Dependency | Required By | Available | Version | Fallback | +|------------|------------|-----------|---------|----------| +| cmake | all builds | ✓ | 3.29.2 | — | +| ninja | all builds | ✓ | 1.13.2 | — | +| clang (Apple) | OSX build | ✓ | 17.0.0 | — | +| gh CLI | CI release downloads + local release inspection | ✓ | 2.93.0 | curl fallback in cmaketemplate auto-download | +| git | submodule ops | ✓ | 2.50.1 | — | +| thirdparty build tree | local builds | ✓ | `../thirdparty` present with build outputs | CI downloads release tarball | +| SuperGenius build tree | local builds | ✓ | `../SuperGenius/build/OSX/Debug` (resolved in existing cache) | CI downloads release | +| GeniusSDK build tree | local builds | ✓ | `../GeniusSDK/build/OSX/Debug` (resolved in existing cache) | CI downloads release | +| Android NDK | Android builds (CI) | ✗ (not checked locally; CI installs r27b per workflow) | r27b in CI | CI step downloads it | +| Existing local build proof | — | ✓ | `build/OSX/Debug/gcs_src/libgcs_core.a` exists | — | + +**Missing dependencies with no fallback:** none for the planned work. +**Missing dependencies with fallback:** Android NDK locally (CI installs it; local Android builds need `ANDROID_NDK_HOME` set). + +## Validation Architecture + +### Test Framework +| Property | Value | +|----------|-------| +| Framework | Google Test (thirdparty-provided; `find_package(GTest CONFIG REQUIRED)` in root CMakeLists under `BUILD_TESTING`) | +| Config file | none for GCS yet — GCS-root `test/CMakeLists.txt` is a Wave 0 deliverable; `build/CommonBuildParameters.cmake:146-151` already has the `add_subdirectory(${PROJECT_ROOT}/test ...)` hook gated on `BUILD_TESTS`/`BUILD_TESTING` | +| Quick run command | `ctest --test-dir build/OSX/Debug -R gcs --output-on-failure` | +| Full suite command | `ctest --test-dir build/OSX/Debug --output-on-failure` (includes neoswarm tests, which also build in the parent build) | + +### Phase Requirements → Test Map +| Req ID | Behavior | Test Type | Automated Command | File Exists? | +|--------|----------|-----------|-------------------|-------------| +| CORE-05 | GlobalDB CRDT initializes; broadcast+listen topic wired; Put→Get round-trips locally over real GossipPubSub (port 0) | unit (integration-style fixture) | `ctest --test-dir build/OSX/Debug -R test_gcs_core_smoke --output-on-failure` | ❌ Wave 0 | +| (success criterion 3) | FFI shared lib loads; `gcs_init` returns handle; echo/round-trip call works; shutdown clean | unit (links gcs_ffi) | `ctest --test-dir build/OSX/Debug -R test_gcs_ffi --output-on-failure` | ❌ Wave 0 | +| (success criterion 4) | `gcs_join_topic` / GlobalDB `AddBroadcastTopic`+`AddListenTopic` succeed on a `gcs/chat/*` topic | covered by CORE-05 smoke test | same as CORE-05 | ❌ Wave 0 | +| (success criteria 1+5) | All 5 platforms build + ctest green | CI | workflow matrix (OSX/Linux/Windows run ctest; Android/iOS build-only) | ❌ Wave 0 (`.github/workflows/cmake.yml`) | + +### Sampling Rate +- **Per task commit:** `cmake --build build/OSX/Debug -j && ctest --test-dir build/OSX/Debug -R gcs --output-on-failure` +- **Per wave merge:** `ctest --test-dir build/OSX/Debug --output-on-failure` (full parent-build suite) +- **Phase gate:** Full suite green locally on OSX + CI green on all 5 platforms before `/gsd:verify-work` + +### Wave 0 Gaps +- [ ] `test/CMakeLists.txt` (GCS root) — registers smoke tests; gate on GTest found +- [ ] `test/test_wait_condition.hpp` — WaitForCondition template (copy from neoswarm) +- [ ] `test/test_gcs_core_smoke.cpp` — covers CORE-05 substrate (lifecycle + topic + Put/Get round-trip) +- [ ] `test/test_gcs_ffi.cpp` — covers FFI init/echo/shutdown +- [ ] `src/ffi/CMakeLists.txt` + `gcs_ffi` SHARED target +- [ ] `.github/workflows/cmake.yml` — CI (Wave 0 or first wave; without it criterion 5 is unverifiable) + +## Security Domain + +### Applicable ASVS Categories + +| ASVS Category | Applies | Standard Control | +|---------------|---------|-----------------| +| V2 Authentication | no | P2P identity via libp2p keypair (`KeyPairFileStorage`) — no user auth this phase | +| V3 Session Management | partial | FFI session handle: opaque pointer, no serialization, shutdown unregisters callbacks (Pitfall 6) | +| V4 Access Control | no | Membership/roles land in Phase 4 | +| V5 Input Validation | yes | FFI boundary: null-check all `const char*` from Dart, treat as untrusted UTF-8; length-bound copies; mirror the null-tolerant style of `genius_elm_chat_completions.cpp` | +| V6 Cryptography | yes (inherited) | Never hand-roll: libp2p keypair via SuperGenius `KeyPairFileStorage`; app-layer encryption deferred to v1.1 | + +### Known Threat Patterns for C++/FFI/libp2p stack + +| Pattern | STRIDE | Standard Mitigation | +|---------|--------|---------------------| +| Use-after-free across FFI (Dart disposes while C++ posts) | Tampering/DoS | Session mutex + unregister-on-shutdown ordering (Pitfall 6) | +| Heap mismatch (Dart-side free of C++ malloc) | DoS | Paired `gcs_string_free` export; never let Dart free native memory directly | +| Null/malformed strings from Dart | Tampering | Null-check + validate at FFI boundary before touching C++ state | +| Double-shutdown of GcsGlobalDb | DoS | Already handled: `Shutdown()` is idempotent (CITED: gcs_global_db.hpp) | +| Double `gcs_init` | DoS | Mirror ELM pattern: thread-safe, subsequent calls no-op or return existing handle | + +## Sources + +### Primary (HIGH confidence) +- `GNUS-NEO-SWARM/src/storage/gcs_global_db.hpp` + `src/storage/CMakeLists.txt` — component API, linkage contract, dylib redirect +- `GNUS-NEO-SWARM/test/storage/test_gcs_global_db.cpp` — complete working smoke-test recipe (read in full) +- `GNUS-NEO-SWARM/cmake/CommonBuildParameters.cmake:290-479` — GeniusSDK/SuperGenius discovery, auto-download, find_package +- `cmake/CommonBuildParameters.cmake` + `build/CommonCompilerOptions.cmake` + `build/cmake/functions.cmake` (GCS root) — thirdparty discovery, entry-point chain +- `build/OSX/Debug/CMakeCache.txt` — proof the local build configures/builds with sibling-tree deps (libgcs_core.a exists) +- `../GeniusSDK/.github/workflows/cmake.yml` — consumer-repo CI template (read in full) +- `../SuperGenius/.github/workflows/cmake.yml:700-740` — ctest steps with keyring wrapper +- `gh release list` for SuperGenius / GeniusSDK / thirdparty — all 12 matrix combos exist on develop (verified 2026-08-15) +- `../GeniusSDK/src/GeniusSDK.h:200-240`, `GeniusSDK.hpp:22` — `GeniusSDKInit`/`GeniusSDKGetNode` signatures +- `thirdparty/ipfs-pubsub/src/ipfs_pubsub/gossip_pubsub.hpp:110-146`, `../SuperGenius/src/crdt/globaldb/globaldb.hpp:89-225` — pub/sub + CRDT API surface +- Repo-wide greps confirming absence of `Dart_PostCObject`/`NativePort` usage (the D-05 gap) + +### Secondary (MEDIUM confidence) +- None — all load-bearing claims verified against files in this workspace. + +### Tertiary (LOW confidence) +- Dart API_DL include/link strategy for a plain shared lib (A1) — standard pattern from Dart documentation knowledge; spike task planned. + +## Metadata + +**Confidence breakdown:** +- Standard stack: HIGH — no new packages; all components read in-tree +- Architecture: HIGH — build/link/test wiring verified against live files and an existing successful local build +- Pitfalls: HIGH — each pitfall derived from an in-repo comment, CMake guard, or verified file state (including the ffigen.yaml staleness and the missing D-05 precedent) +- FFI callback mechanism: MEDIUM — the pattern is standard, but no in-repo precedent exists (see Open Question 1) + +**Research date:** 2026-08-15 +**Valid until:** 2026-09-14 (30 days; CI release tags roll forward continuously — re-check `gh release list` if planning happens after GeniusSDK publishes newer develop releases) From ca5861776c168597de076af7de5a4bd2b7a753cd Mon Sep 17 00:00:00 2001 From: Super Genius Date: Sat, 15 Aug 2026 19:07:02 -0700 Subject: [PATCH 13/49] docs(phase-1): add validation strategy --- .../app/phases/01-foundation/01-VALIDATION.md | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 .planning/workstreams/app/phases/01-foundation/01-VALIDATION.md diff --git a/.planning/workstreams/app/phases/01-foundation/01-VALIDATION.md b/.planning/workstreams/app/phases/01-foundation/01-VALIDATION.md new file mode 100644 index 0000000..05285dd --- /dev/null +++ b/.planning/workstreams/app/phases/01-foundation/01-VALIDATION.md @@ -0,0 +1,76 @@ +--- +phase: 1 +slug: foundation +status: draft +nyquist_compliant: false +wave_0_complete: false +created: 2026-08-15 +--- + +# Phase 1 — Validation Strategy + +> Per-phase validation contract for feedback sampling during execution. + +--- + +## Test Infrastructure + +| Property | Value | +|----------|-------| +| **Framework** | {pytest 7.x / jest 29.x / vitest / go test / other} | +| **Config file** | {path or "none — Wave 0 installs"} | +| **Quick run command** | `{quick command}` | +| **Full suite command** | `{full command}` | +| **Estimated runtime** | ~{N} seconds | + +--- + +## Sampling Rate + +- **After every task commit:** Run `{quick run command}` +- **After every plan wave:** Run `{full suite command}` +- **Before `/gsd:verify-work`:** Full suite must be green +- **Max feedback latency:** {N} seconds + +--- + +## Per-Task Verification Map + +| Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status | +|---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------| +| {N}-01-01 | 01 | 1 | REQ-{XX} | T-{N}-01 / — | {expected secure behavior or "N/A"} | unit | `{command}` | ✅ / ❌ W0 | ⬜ pending | + +*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky* + +--- + +## Wave 0 Requirements + +- [ ] `{tests/test_file.py}` — stubs for REQ-{XX} +- [ ] `{tests/conftest.py}` — shared fixtures +- [ ] `{framework install}` — if no framework detected + +*If none: "Existing infrastructure covers all phase requirements."* + +--- + +## Manual-Only Verifications + +| Behavior | Requirement | Why Manual | Test Instructions | +|----------|-------------|------------|-------------------| +| {behavior} | REQ-{XX} | {reason} | {steps} | + +*If none: "All phase behaviors have automated verification."* + +--- + +## Validation Sign-Off + +- [ ] All tasks have `` verify or Wave 0 dependencies +- [ ] Sampling continuity: no 3 consecutive tasks without automated verify +- [ ] Wave 0 covers all MISSING references +- [ ] No watch-mode flags +- [ ] Feedback latency < {N}s +- [ ] `nyquist_compliant: true` set in frontmatter + +**Approval:** {pending / approved YYYY-MM-DD} From ccbaaeac8947f16110f176836acfcc762b1213b3 Mon Sep 17 00:00:00 2001 From: "Claude (GSD planner)" Date: Sat, 15 Aug 2026 19:34:24 -0700 Subject: [PATCH 14/49] fix(01): revise phase 1 plans based on checker feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 01-RESEARCH.md: mark Open Questions RESOLVED (Q1 -> Plan 01-05 spike; Q2 -> Plan 01-01 accessors; Q3 -> cmaketemplate auto-download, no explicit zkLLVM CI step per D-09 interpretation) - 01-VALIDATION.md: fill per-task verification map from plan automated blocks; set status: ready; nyquist_compliant: true - Wave/depends_on rebucket: 01-02 wave 2 depends_on [01-01]; 01-03 wave 3 depends_on [01-01, 01-02]; 01-04 wave 3; 01-05 wave 4; 01-06 wave 5 - 01-01 Task 3: pin branch stacking — feature/gcs-globaldb-passthrough created FROM feature/app-restructure HEAD (locked at planning time) - 01-04 Task 3: commit to option C — gcs_init returns nullptr gracefully without GeniusSDK node; remove all gcs_on_message tests (moved to 01-05); drop init-success EXPECT_NE assertions - 01-05: owns all on_message tests; rephrase success criteria — no partial pass, Dart smoke suite passing is the pass condition with skip: only for live-node round-trip - 01-06: pin iOS toolchain — no -DCMAKE_TOOLCHAIN_FILE flag; build/iOS CMakeLists auto-sets to build/apple.toolchain.cmake - ROADMAP: annotate plan list with wave numbers and dependency notes --- .planning/workstreams/app/ROADMAP.md | 12 +- .../app/phases/01-foundation/01-01-PLAN.md | 258 +++++++++++++ .../app/phases/01-foundation/01-02-PLAN.md | 300 +++++++++++++++ .../app/phases/01-foundation/01-03-PLAN.md | 311 +++++++++++++++ .../app/phases/01-foundation/01-04-PLAN.md | 322 ++++++++++++++++ .../app/phases/01-foundation/01-05-PLAN.md | 361 ++++++++++++++++++ .../app/phases/01-foundation/01-06-PLAN.md | 285 ++++++++++++++ .../app/phases/01-foundation/01-RESEARCH.md | 5 +- .../app/phases/01-foundation/01-VALIDATION.md | 72 ++-- 9 files changed, 1898 insertions(+), 28 deletions(-) create mode 100644 .planning/workstreams/app/phases/01-foundation/01-01-PLAN.md create mode 100644 .planning/workstreams/app/phases/01-foundation/01-02-PLAN.md create mode 100644 .planning/workstreams/app/phases/01-foundation/01-03-PLAN.md create mode 100644 .planning/workstreams/app/phases/01-foundation/01-04-PLAN.md create mode 100644 .planning/workstreams/app/phases/01-foundation/01-05-PLAN.md create mode 100644 .planning/workstreams/app/phases/01-foundation/01-06-PLAN.md diff --git a/.planning/workstreams/app/ROADMAP.md b/.planning/workstreams/app/ROADMAP.md index 26b3a95..9460bf0 100644 --- a/.planning/workstreams/app/ROADMAP.md +++ b/.planning/workstreams/app/ROADMAP.md @@ -32,7 +32,15 @@ Decimal phases appear between their surrounding integers in numeric order. 3. Flutter app can call into C++ via FFI and receive a response 4. GossipSub topics can be created and joined from the C++ core 5. CI/CD pipeline builds and tests on self-hosted runners for macOS, Linux, Windows, iOS, Android -**Plans**: TBD +**Plans:** 6 plans + +Plans: +- [ ] 01-01-PLAN.md — Wave 1 — GcsGlobalDb pass-through accessors (GNUS-NEO-SWARM submodule branch, stacked on feature/app-restructure) +- [ ] 01-02-PLAN.md — Wave 2 (depends: 01-01) — Wire gcs_core to neoswarm_storage; create gcs::CoreSession class +- [ ] 01-03-PLAN.md — Wave 3 (depends: 01-01, 01-02) — gcs_ffi SHARED library + single opaque-handle C API +- [ ] 01-04-PLAN.md — Wave 3 (depends: 01-01, 01-02) — Test scaffolding + CORE-05 smoke tests (lifecycle, CRDT round-trip, FFI option-C init) +- [ ] 01-05-PLAN.md — Wave 4 (depends: 01-03, 01-04) — Dart NativePort callback spike + ffigen bindings + Dart smoke test (owns all on_message tests) +- [ ] 01-06-PLAN.md — Wave 5 (depends: 01-03, 01-04) — CI workflow (.github/workflows/cmake.yml) for all 5 platforms ### Phase 2: Spaces & Rooms **Goal**: Users can create spaces and rooms with configurable inheritance, and the entity hierarchy is persisted via CRDT. @@ -115,7 +123,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| -| 1. Foundation | 0/TBD | Not started | - | +| 1. Foundation | 0/6 | Not started | - | | 2. Spaces & Rooms | 0/TBD | Not started | - | | 3. Messaging | 0/TBD | Not started | - | | 4. Membership & Invites | 0/TBD | Not started | - | diff --git a/.planning/workstreams/app/phases/01-foundation/01-01-PLAN.md b/.planning/workstreams/app/phases/01-foundation/01-01-PLAN.md new file mode 100644 index 0000000..96d7771 --- /dev/null +++ b/.planning/workstreams/app/phases/01-foundation/01-01-PLAN.md @@ -0,0 +1,258 @@ +--- +phase: 01-foundation +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - GNUS-NEO-SWARM/src/storage/gcs_global_db.hpp + - GNUS-NEO-SWARM/src/storage/gcs_global_db.cpp +autonomous: true +requirements: [CORE-05] +must_haves: + truths: + - "GcsGlobalDb exposes AddBroadcastTopic, AddListenTopic, Put, Get as public pass-throughs to the underlying sgns::crdt::GlobalDB" + - "All four accessors return outcome::failure(Error::GcsDbError) when called before Initialize()" + - "The submodule change is committed on a GNUS-NEO-SWARM branch (not on the GCS repo directly)" + artifacts: + - path: "GNUS-NEO-SWARM/src/storage/gcs_global_db.hpp" + provides: "Public pass-through declarations on GcsGlobalDb" + contains: "AddBroadcastTopic" + - path: "GNUS-NEO-SWARM/src/storage/gcs_global_db.cpp" + provides: "Pass-through implementations guarding on m_running" + contains: "m_db->AddBroadcastTopic" + key_links: + - from: "GNUS-NEO-SWARM/src/storage/gcs_global_db.cpp" + to: "sgns::crdt::GlobalDB" + via: "m_db->AddBroadcastTopic / AddListenTopic / Put / Get" + pattern: "m_db->(AddBroadcastTopic|AddListenTopic|Put|Get)" +--- + + +Add minimal pass-through accessors (`AddBroadcastTopic`, `AddListenTopic`, `Put`, `Get`) to +`GcsGlobalDb` in the GNUS-NEO-SWARM submodule so Phase 1 can smoke-test CORE-05's CRDT substrate +(topic wiring + Put/Get round-trip) from the GCS root without exposing the underlying +`sgns::crdt::GlobalDB` pointer. + +Purpose: Unblocks the GCS-side smoke test (Plan 04) and CORE-05 success criterion 2 ("GlobalDB CRDT +instance can be initialized and a test op can be published/subscribed locally"). Approved per +Open Question 2 in 01-RESEARCH.md (user has accepted a neoswarm branch + PR for this). + +Output: 2 files modified in GNUS-NEO-SWARM; committed on a neoswarm feature branch; submodule +pointer bump on GCS `develop` is deferred to the end of the phase (after Plan 04's smoke test +proves the accessors work). + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/workstreams/app/STATE.md +@.planning/workstreams/app/ROADMAP.md +@.planning/workstreams/app/phases/01-foundation/01-CONTEXT.md +@.planning/workstreams/app/phases/01-foundation/01-RESEARCH.md +@.planning/workstreams/app/phases/01-foundation/01-PATTERNS.md + +@GNUS-NEO-SWARM/src/storage/gcs_global_db.hpp +@GNUS-NEO-SWARM/src/storage/gcs_global_db.cpp +@GNUS-NEO-SWARM/src/common/error.hpp + + + + +From GNUS-NEO-SWARM/src/storage/gcs_global_db.hpp: +```cpp +class GcsGlobalDb { +public: + struct Config { std::string m_dbPath; /* ... */ }; + outcome::result Initialize(); // production (uses GeniusSDKGetNode()) + outcome::result Initialize(std::shared_ptr pubsub); // test seam + outcome::result Shutdown(); + bool IsRunning() const; +private: + std::shared_ptr m_db; + std::atomic m_running{false}; + // ... +}; +``` + +Underlying GlobalDB API to wrap (from ../SuperGenius/src/crdt/globaldb/globaldb.hpp:89-225): +```cpp +outcome::result AddBroadcastTopic(const std::string& topicName); +outcome::result AddListenTopic(const std::string& topicName); +outcome::result Put(const HierarchicalKey& key, const Buffer& value); +outcome::result Get(const HierarchicalKey& key) const; +``` + +Error mapping (from GNUS-NEO-SWARM/src/common/error.hpp): use `Error::GcsDbError` for both +"not running" and any underlying GlobalDB failure (D-14 in neoswarm Phase 3 conventions). + + + + + + + Task 1: Add four pass-through accessors to GcsGlobalDb (hpp declarations + doxygen) + GNUS-NEO-SWARM/src/storage/gcs_global_db.hpp + + - GNUS-NEO-SWARM/src/storage/gcs_global_db.hpp (the file being modified — full read) + - GNUS-NEO-SWARM/src/common/error.hpp (Error enum; confirm GcsDbError exists) + - .planning/workstreams/app/phases/01-foundation/01-PATTERNS.md § "GNUS-NEO-SWARM submodule — pass-through accessors" (exact signature shape and Doxygen block) + + + Per PATTERNS.md "GNUS-NEO-SWARM submodule" section and RESEARCH Open Question 2. + + In the `public:` section of `GcsGlobalDb`, immediately AFTER the existing `IsRunning()` declaration, + insert four new method declarations with full Doxygen headers (project rule: every public API): + + outcome::result AddBroadcastTopic( const std::string &topicName ); + outcome::result AddListenTopic( const std::string &topicName ); + outcome::result Put( const std::string &key, const std::string &value ); + outcome::result Get( const std::string &key ); + + Each Doxygen block must state: + - \brief line naming the wrapped GlobalDB call (e.g. "Add a broadcast topic — pass-through to sgns::crdt::GlobalDB::AddBroadcastTopic") + - \param[in] entries for each parameter + - \return stating: "outcome::success on success, Error::GcsDbError if not running or if the underlying call fails" + + Do NOT change existing declarations. Do NOT add new private members. Do NOT add new includes — + std::string and outcome::result are already in scope per the existing file. + + Follow the neoswarm C++ style: Allman braces, space inside parens, `m_` prefix member naming, + camelCase args, 120-char lines, C++17 ceiling. + + + grep -c "AddBroadcastTopic\|AddListenTopic\|::Put(\|::Get(" GNUS-NEO-SWARM/src/storage/gcs_global_db.hpp | grep -q "^[4-9]\|^[1-9][0-9]" + + + - File contains exactly four new declarations with names AddBroadcastTopic, AddListenTopic, Put, Get + - Each declaration is preceded by a Doxygen comment block containing \brief, \param[in], \return + - No existing declaration is modified or removed + - Return types match exactly: outcome::result<void> for the first three, outcome::result<std::string> for Get + + Four public pass-throughs declared on GcsGlobalDb with Doxygen; no other lines changed. + + + + Task 2: Implement the four pass-throughs in gcs_global_db.cpp + GNUS-NEO-SWARM/src/storage/gcs_global_db.cpp + + - GNUS-NEO-SWARM/src/storage/gcs_global_db.cpp (the file being modified — full read; note the existing m_running guard pattern used by Shutdown) + - GNUS-NEO-SWARM/src/storage/gcs_global_db.hpp (declarations from Task 1) + - ../SuperGenius/src/crdt/globaldb/globaldb.hpp (underlying signatures for HierarchicalKey / Buffer construction) + + + Append four new member function definitions at the END of GNUS-NEO-SWARM/src/storage/gcs_global_db.cpp, + each following the exact guard shape already used by `Shutdown()` (check m_running, bail with + Error::GcsDbError if false), then forward to m_db. + + Implementation shape (per PATTERNS.md): + + outcome::result GcsGlobalDb::AddBroadcastTopic( const std::string &topicName ) + { + if ( !m_running.load() ) + { + return outcome::failure( Error::GcsDbError ); + } + return m_db->AddBroadcastTopic( topicName ); + } + + AddListenTopic: identical shape. + + Put: convert std::string to GlobalDB types at the call site: + - HierarchicalKey from key string + - Buffer from value string (Buffer{}.put(value) or the GlobalDB-idiomatic conversion — copy the exact pattern from any existing SuperGenius call site of GlobalDB::Put; if unsure, ASK the user before guessing) + + Get: call m_db->Get( HierarchicalKey{ key } ), on success convert the returned Buffer to std::string + (Buffer::toString() or the equivalent idiom — verify against SuperGenius usage). + + Match the project's error-mapping rule: ANY underlying failure (not just !m_running) maps to + Error::GcsDbError (D-14 in neoswarm). If the underlying call returns outcome::failure, propagate + outcome::failure( Error::GcsDbError ). + + Allman braces, space inside parens, C++17 only. Do NOT add log statements unless the existing + file already logs at this level (keep the diff minimal). + + + cd GNUS-NEO-SWARM && git diff --stat HEAD && grep -c "GcsGlobalDb::AddBroadcastTopic\|GcsGlobalDb::AddListenTopic\|GcsGlobalDb::Put\|GcsGlobalDb::Get" src/storage/gcs_global_db.cpp | grep -q "^4$" + + + - Exactly four new member-function definitions added + - Every definition starts with the m_running.load() guard returning Error::GcsDbError + - Every definition calls the matching m_db-> method + - Allman braces; space inside parens; 120-char max line length + - File still compiles (verified by the submodule's existing build, or at minimum by syntax inspection if submodule-only build is unavailable — note this in the plan summary) + + Four implementations committed on the neoswarm feature branch; ready for GCS-side consumption. + + + + Task 3: Commit accessors on a neoswarm feature branch (no GCS-side pointer bump yet) + GNUS-NEO-SWARM (submodule .git metadata only) + + - GNUS-NEO-SWARM/.git (current branch state) + - .planning/workstreams/app/phases/01-foundation/01-CONTEXT.md (D-03: neoswarm changes stay isolated this phase) + + + Branch stacking decision (locked at planning time, 2026-08-15): the GNUS-NEO-SWARM + submodule is currently on `feature/app-restructure`. The passthrough branch + `feature/gcs-globaldb-passthrough` is created FROM the current `feature/app-restructure` + HEAD — i.e. stacked on it. Reason: `feature/app-restructure` holds the approved repo + restructure and the neoswarm workstream is mid-Phase-3 on it; branching from `develop` + would lose that context and the two workstreams share the submodule. The passthrough + branch merges to `develop` alongside or after `feature/app-restructure` — it does NOT + need to land first, and it must NOT be rebased onto `develop` while `app-restructure` + is in flight. + + Inside GNUS-NEO-SWARM (concrete commands — no conditionals): + + 1. Confirm we are on the stacking base: + git -C GNUS-NEO-SWARM branch --show-current + Expected output: `feature/app-restructure`. If the output is anything else, STOP + and confirm with the user before proceeding — the stacking assumption is broken. + 2. Create and switch to the passthrough branch from current HEAD: + git -C GNUS-NEO-SWARM checkout -b feature/gcs-globaldb-passthrough + 3. Stage the two modified files: + git -C GNUS-NEO-SWARM add src/storage/gcs_global_db.hpp src/storage/gcs_global_db.cpp + 4. Commit with a conventional message: + git -C GNUS-NEO-SWARM commit -m "feat(storage): add AddBroadcastTopic/AddListenTopic/Put/Get pass-throughs to GcsGlobalDb" + 5. Do NOT push yet — the user reviews and pushes per project rule ("Do not commit changes + without explicit user permission" applies at push time; the local commit is the deliverable). + + Do NOT modify the GCS-root submodule pointer (no `git add GNUS-NEO-SWARM` in the parent repo). + The pointer bump happens at end of phase after Plan 04's smoke test proves the new API works. + + + git -C GNUS-NEO-SWARM log -1 --oneline | grep -q "pass-throughs to GcsGlobalDb" + + + - GNUS-NEO-SWARM is on branch feature/gcs-globaldb-passthrough + - feature/gcs-globaldb-passthrough's merge-base with feature/app-restructure equals feature/app-restructure's HEAD (i.e. it is stacked on it, NOT on develop) + - HEAD commit message contains "pass-throughs to GcsGlobalDb" + - Commit touches exactly two files (gcs_global_db.hpp, gcs_global_db.cpp) + - GCS-root `git status` shows GNUS-NEO-SWARM as modified-submodule (expected; pointer NOT staged) + + Local commit on neoswarm feature branch; submodule pointer unchanged in GCS root. + + + + + +After all three tasks: +- `git -C GNUS-NEO-SWARM diff develop..HEAD --stat` shows exactly 2 files changed +- The four accessors compile cleanly as part of the GCS parent build (validated in Plan 04 when + test_gcs_core_smoke calls them; this plan does not itself run a build — that requires CoreSession + wiring from Plan 02) + + + +GcsGlobalDb's public API now includes the four pass-throughs, callable from gcs_core, with +correct not-running guards, and the change is isolated on a neoswarm feature branch pending +user review + push. + + + +Create `.planning/workstreams/app/phases/01-foundation/01-01-SUMMARY.md` when done. + diff --git a/.planning/workstreams/app/phases/01-foundation/01-02-PLAN.md b/.planning/workstreams/app/phases/01-foundation/01-02-PLAN.md new file mode 100644 index 0000000..3c16eb6 --- /dev/null +++ b/.planning/workstreams/app/phases/01-foundation/01-02-PLAN.md @@ -0,0 +1,300 @@ +--- +phase: 01-foundation +plan: 02 +type: execute +wave: 2 +depends_on: ["01-01"] +files_modified: + - src/CMakeLists.txt + - src/lib/gcs_core.hpp + - src/lib/gcs_core.cpp +autonomous: true +requirements: [CORE-05] +must_haves: + truths: + - "gcs_core static library links PUBLIC against neoswarm_storage (transitively sgns::crdt_globaldb + sgns::GeniusSDK_shared)" + - "src/lib/gcs_core.hpp declares a gcs::CoreSession class with Config, Initialize(), Initialize(pubsub), Shutdown(), IsRunning(), AddBroadcastTopic, AddListenTopic, Put, Get" + - "src/lib/gcs_core.cpp implements CoreSession by delegating to GcsGlobalDb" + - "The existing local build (`cmake --build build/OSX/Debug -j`) still succeeds after the change" + artifacts: + - path: "src/CMakeLists.txt" + provides: "Hard-required linkage of gcs_core → neoswarm_storage (FATAL_ERROR if missing)" + contains: "neoswarm_storage" + - path: "src/lib/gcs_core.hpp" + provides: "gcs::CoreSession public interface" + contains: "class CoreSession" + - path: "src/lib/gcs_core.cpp" + provides: "CoreSession implementation delegating to GcsGlobalDb" + contains: "GcsGlobalDb" + key_links: + - from: "src/CMakeLists.txt" + to: "neoswarm_storage" + via: "target_link_libraries(gcs_core PUBLIC neoswarm_storage)" + pattern: "target_link_libraries\\(gcs_core PUBLIC neoswarm_storage" + - from: "src/lib/gcs_core.cpp" + to: "GNUS-NEO-SWARM/src/storage/gcs_global_db.hpp" + via: "m_db member of type GcsGlobalDb" + pattern: "storage/gcs_global_db.hpp" +--- + + +Wire `gcs_core` to `neoswarm_storage` (per RESEARCH § "How gcs_core links GlobalDB") and replace +the placeholder `gcs_core.cpp` with a real `gcs::CoreSession` class that owns a `GcsGlobalDb` +instance and exposes lifecycle + CRDT accessors to the upcoming FFI layer. + +Purpose: This is the C++ "core session" that D-01/D-04 mandate: C++ owns all state, Dart will get +a thin wrapper. Phase 1 scope of CoreSession is intentionally minimal — init, shutdown, isRunning, +plus pass-throughs for the four CRDT ops added to GcsGlobalDb in Plan 01. No message logic, no +rooms/spaces — those land in Phases 2-3. + +Output: `gcs_core` static lib builds with a real session class ready for `gcs_ffi` (Plan 03) and +the smoke test (Plan 04). + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/workstreams/app/STATE.md +@.planning/workstreams/app/ROADMAP.md +@.planning/workstreams/app/phases/01-foundation/01-CONTEXT.md +@.planning/workstreams/app/phases/01-foundation/01-RESEARCH.md +@.planning/workstreams/app/phases/01-foundation/01-PATTERNS.md + +@src/CMakeLists.txt +@src/lib/gcs_core.cpp +@GNUS-NEO-SWARM/src/storage/gcs_global_db.hpp +@GNUS-NEO-SWARM/src/storage/CMakeLists.txt + + + + +From GNUS-NEO-SWARM/src/storage/gcs_global_db.hpp: +```cpp +class GcsGlobalDb { +public: + struct Config { std::string m_dbPath; /* ... */ }; + outcome::result Initialize(); + outcome::result Initialize(std::shared_ptr pubsub); + outcome::result Shutdown(); + bool IsRunning() const; + // After Plan 01: AddBroadcastTopic, AddListenTopic, Put, Get +}; +``` + +From GNUS-NEO-SWARM/src/storage/CMakeLists.txt (the hard-required linkage idiom to copy): +```cmake +if(TARGET sgns::crdt_globaldb) + target_link_libraries(neoswarm_storage PUBLIC sgns::crdt_globaldb ...) +else() + message(FATAL_ERROR "...") +endif() +``` + + + + + + + Task 1: Wire gcs_core → neoswarm_storage in src/CMakeLists.txt (hard-required idiom) + src/CMakeLists.txt + + - src/CMakeLists.txt (the file being modified — currently 16 lines) + - GNUS-NEO-SWARM/src/storage/CMakeLists.txt lines 14-18 (the FATAL_ERROR idiom to copy) + - .planning/workstreams/app/phases/01-foundation/01-PATTERNS.md § "src/CMakeLists.txt (modify — extend existing target)" + + + Edit src/CMakeLists.txt. After the existing `target_link_libraries(gcs_core PUBLIC spdlog::spdlog fmt::fmt)` block, append: + + # neoswarm_storage — hard required (project Conditional Compilation rule: missing required + # libraries fail at configure time, never degrade to a stub). + if(TARGET neoswarm_storage) + target_link_libraries(gcs_core PUBLIC neoswarm_storage) + else() + message(FATAL_ERROR "neoswarm_storage target not found — GNUS-NEO-SWARM must be configured before gcs_core") + endif() + + add_subdirectory(ffi) + + Notes: + - `add_subdirectory(ffi)` is added now even though src/ffi/CMakeLists.txt is created in Plan 03 — + CMake configure will fail until Plan 03 lands, so plan the commit accordingly: commit this + CMakeLists change together with Plan 03's `src/ffi/CMakeLists.txt` (Plan 03 is in Wave 2; + this plan's commit should hold the add_subdirectory line but Plan 02 must NOT include the + add_subdirectory line in its own commit — leave it commented out OR split the commit). + - Resolution: leave the `add_subdirectory(ffi)` line OUT of this plan. Plan 03 (which creates + src/ffi/CMakeLists.txt) adds the line at the same time. This keeps each commit buildable. + - The PUBLIC scope is intentional: `gcs_ffi` (Plan 03) will `#include "lib/gcs_core.hpp"` and + needs neoswarm_storage's PUBLIC include dirs transitively for GcsGlobalDb's signature types. + + Do NOT modify the existing gcs_core target sources, includes, or the spdlog/fmt links. + + + grep -A1 "if(TARGET neoswarm_storage" src/CMakeLists.txt | grep -q "target_link_libraries(gcs_core PUBLIC neoswarm_storage)" + + + - src/CMakeLists.txt contains the `if(TARGET neoswarm_storage) ... else() message(FATAL_ERROR ...)` block + - The `target_link_libraries(gcs_core PUBLIC neoswarm_storage)` line is inside the if-branch + - The file does NOT yet contain `add_subdirectory(ffi)` (that lands in Plan 03) + - Existing 16 lines of the file are unchanged (the only diff is the appended block) + + gcs_core is wired to neoswarm_storage with a configure-time hard fail when missing. + + + + Task 2: Create src/lib/gcs_core.hpp declaring gcs::CoreSession + src/lib/gcs_core.hpp + + - src/lib/gcs_core.cpp (existing placeholder being replaced) + - GNUS-NEO-SWARM/src/storage/gcs_global_db.hpp (class shape, Config pattern, lifecycle method style) + - GNUS-NEO-SWARM/src/genius_elm_chat_completions.h (Doxygen style: \brief, \param[in], \return) + - .planning/workstreams/app/phases/01-foundation/01-PATTERNS.md § "C++ Style (SuperGenius Naming)" + + + Create src/lib/gcs_core.hpp (new file) declaring: + + #ifndef GENIUS_COGNITIVE_SYSTEM_GCS_CORE_HPP + #define GENIUS_COGNITIVE_SYSTEM_GCS_CORE_HPP + + #include + #include + + #include // already on the include path via neoswarm_storage + #include "storage/gcs_global_db.hpp" // neoswarm_storage PUBLIC include dir + + namespace sgns::ipfs_pubsub { class GossipPubSub; } // forward decl for the test-seam overload + + namespace gcs + { + class CoreSession + { + public: + struct Config + { + std::string m_dbPath; + }; + + explicit CoreSession( Config config ); + ~CoreSession(); + + CoreSession( const CoreSession & ) = delete; + CoreSession &operator=( const CoreSession & ) = delete; + + outcome::result Initialize(); + outcome::result Initialize( std::shared_ptr pubsub ); + outcome::result Shutdown(); + bool IsRunning() const; + + outcome::result AddBroadcastTopic( const std::string &topicName ); + outcome::result AddListenTopic( const std::string &topicName ); + outcome::result Put( const std::string &key, const std::string &value ); + outcome::result Get( const std::string &key ); + + private: + Config m_config; + std::unique_ptr m_db; // adjust namespace per actual GcsGlobalDb namespace + }; + } // namespace gcs + + #endif + + Doxygen headers on the class and on every public method (project rule). Match the existing + GcsGlobalDb Doxygen style — \brief, \param[in], \return. + + IMPORTANT — verify the actual namespace of GcsGlobalDb before declaring the m_db member. + It may be `sgns::crdt::GcsGlobalDb`, `sgns::gcs::GcsGlobalDb`, or un-namespaced. Read the + header to confirm and use the exact namespace. Do NOT guess. + + Style: Allman braces, space inside parens, `m_` member prefix, 120-char max, C++17 only. + No `noexcept` on outcome-returning methods (matches GcsGlobalDb). No magic numbers — none + needed here. + + + grep -c "class CoreSession\|Initialize\|Shutdown\|IsRunning\|AddBroadcastTopic\|AddListenTopic\|Put\|Get" src/lib/gcs_core.hpp | grep -q "^[8-9]\|^[1-9][0-9]" + + + - File compiles as a header (syntax-checked as part of Task 3 build) + - Class name is exactly `gcs::CoreSession` + - Both Initialize overloads present (no-arg + injected-pubsub test seam) + - All four CRDT pass-throughs present with the exact names listed + - Header guard uses GENIUS_COGNITIVE_SYSTEM_GCS_CORE_HPP + - Doxygen on every public method + + CoreSession interface declared; ready for implementation. + + + + Task 3: Replace src/lib/gcs_core.cpp placeholder with CoreSession implementation + src/lib/gcs_core.cpp + + - src/lib/gcs_core.cpp (existing placeholder — full replacement is justified because the file's own comment says "keeps the gcs_core target buildable in the meantime") + - src/lib/gcs_core.hpp (from Task 2) + - GNUS-NEO-SWARM/src/storage/gcs_global_db.cpp (implementation style: outcome returns, m_running guard idiom) + + + Replace the entire contents of src/lib/gcs_core.cpp with the CoreSession implementation. + Keep the existing file's Doxygen header block but update \brief and \details to reflect that + this is now the real CoreSession implementation (not a placeholder). + + Implementation requirements: + - Constructor: stores Config by move, constructs m_db via std::make_unique with the + GcsGlobalDb::Config{ .m_dbPath = m_config.m_dbPath } mapping. Do NOT call Initialize here — + construction must be cheap (deferred registration idiom per CLAUDE.md). + - Destructor: if IsRunning(), call Shutdown() (defensive; matches GcsGlobalDb teardown order). + - Initialize(): delegates to m_db->Initialize(). On failure, propagate the error. + - Initialize(pubsub): delegates to m_db->Initialize(pubsub). Test seam. + - Shutdown(): delegates to m_db->Shutdown() if IsRunning(); else outcome::success (idempotent). + - IsRunning(): m_db->IsRunning(). + - AddBroadcastTopic / AddListenTopic / Put / Get: thin pass-throughs to m_db. These methods + exist on GcsGlobalDb ONLY after Plan 01 lands — sequence this plan after Plan 01 OR use + SFINAE-free direct delegation and accept that Plan 02's local build will not succeed until + Plan 01's submodule commit is present in the working tree. CHOOSE: this plan documents that + it depends on Plan 01's submodule branch being checked out locally; the Wave 1 grouping + assumes both run concurrently with the developer having both branches active. If Plan 01 + is not yet merged into GNUS-NEO-SWARM/develop, the build verification in this task uses + the feature/gcs-globaldb-passthrough branch of the submodule. + + No logging calls (GcsGlobalDb already logs internally at the right levels — adding more is + scope creep). No try/catch — outcome-based error handling throughout. + + Style: Allman braces, space inside parens, 120-char max, C++17. + + Build verification: + cmake --build build/OSX/Debug -j + must succeed. If the submodule branch is not checked out, the build will fail on the four + missing GcsGlobalDb accessors — that failure is EXPECTED until Plan 01 is applied locally. + Document this in the plan summary if it occurs. + + + cmake --build build/OSX/Debug -j 2>&1 | tail -20 | grep -E "error|FAILED" ; test ${PIPESTATUS[0]} -eq 0 + + + - Placeholder namespace/anchor symbol is GONE (no `kGcsCoreVersion` remaining) + - File contains `CoreSession::CoreSession`, `CoreSession::~CoreSession`, both `Initialize` definitions, `Shutdown`, `IsRunning`, and the four pass-through definitions + - Build succeeds on OSX Debug (assuming Plan 01 submodule branch checked out) + - No new #include of platform-specific headers; no OS ifdefs; no sleep_for; no magic numbers + + gcs_core static lib builds with a working CoreSession; ready for gcs_ffi (Plan 03) and smoke tests (Plan 04). + + + + + +End-to-end check after this plan: `cmake --build build/OSX/Debug -j` completes with no errors and +produces `build/OSX/Debug/gcs_src/libgcs_core.a` with CoreSession symbols. Symbol check: + nm build/OSX/Debug/gcs_src/libgcs_core.a | grep -c "CoreSession" +returns > 0. + + + +- src/CMakeLists.txt has the hard-required neoswarm_storage link +- gcs::CoreSession is a real class (not placeholder) +- Build is green locally on OSX +- Plan 03 (gcs_ffi) and Plan 04 (smoke tests) can proceed against this interface + + + +Create `.planning/workstreams/app/phases/01-foundation/01-02-SUMMARY.md` when done. + diff --git a/.planning/workstreams/app/phases/01-foundation/01-03-PLAN.md b/.planning/workstreams/app/phases/01-foundation/01-03-PLAN.md new file mode 100644 index 0000000..4b1bdec --- /dev/null +++ b/.planning/workstreams/app/phases/01-foundation/01-03-PLAN.md @@ -0,0 +1,311 @@ +--- +phase: 01-foundation +plan: 03 +type: execute +wave: 3 +depends_on: ["01-01", "01-02"] +files_modified: + - src/CMakeLists.txt + - src/ffi/CMakeLists.txt + - src/ffi/gcs_core.h + - src/ffi/gcs_core_ffi.cpp +autonomous: true +requirements: [CORE-05] +must_haves: + truths: + - "gcs_ffi SHARED library target builds and links against gcs_core" + - "src/ffi/gcs_core.h declares a single opaque-handle C session API: gcs_init, gcs_shutdown, gcs_join_topic, gcs_publish, gcs_on_message (declaration only — implementation stubbed pending Plan 05 spike), gcs_string_free" + - "All FFI functions are noexcept, null-tolerant, and guarded by a global mutex (per genius_elm_chat_completions idiom)" + - "The GCS_FFI_API export macro is the ONLY permitted OS preprocessor guard in the new files" + - "Build produces libgcs_ffi.{dylib,so,dll} in the build tree" + artifacts: + - path: "src/ffi/CMakeLists.txt" + provides: "gcs_ffi SHARED target with GCS_FFI_EXPORTS define and Android 16k-page link option" + contains: "add_library(gcs_ffi SHARED" + - path: "src/ffi/gcs_core.h" + provides: "Single extern \"C\" opaque-handle C API" + contains: "GcsSession" + - path: "src/ffi/gcs_core_ffi.cpp" + provides: "FFI thunk implementing the C API over gcs::CoreSession" + contains: "gcs_init" + - path: "src/CMakeLists.txt" + provides: "add_subdirectory(ffi) wiring" + contains: "add_subdirectory(ffi)" + key_links: + - from: "src/ffi/gcs_core_ffi.cpp" + to: "src/lib/gcs_core.hpp" + via: "gcs::CoreSession construction and method calls" + pattern: "gcs::CoreSession" + - from: "src/ffi/CMakeLists.txt" + to: "gcs_core" + via: "target_link_libraries(gcs_ffi PRIVATE gcs_core)" + pattern: "target_link_libraries\\(gcs_ffi PRIVATE gcs_core" +--- + + +Create the `gcs_ffi` SHARED library target with a single opaque-handle C API (per D-01, D-02) +that Flutter will load via `DynamicLibrary.open`. This is the only C ABI surface Phase 1 ships. + +Purpose: Flutter talks to C++ exclusively through this header. The opaque `GcsSession*` handle +keeps ownership in C++ (D-04) and the export-macro / `extern "C"` / paired-free / global-mutex / +noexcept idioms are copied verbatim from `genius_elm_chat_completions.{h,cpp}`. + +The `gcs_on_message` function is DECLARED here (so the C ABI is stable and ffigen can be run) +but its IMPLEMENTATION is stubbed to return a "not yet implemented" status code — the Dart +NativePort callback wiring is intentionally deferred to Plan 05's spike (per Open Question 1, +no in-repo precedent exists). Adding the function declaration now keeps the C ABI stable so +callbacks can be added without breaking the ABI later (per planning_context constraint 5). + +Output: `gcs_ffi` builds and exports the session API. Smoke-tested by Plan 04's +test_gcs_ffi.cpp. Dart-side callback spike in Plan 05. + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/workstreams/app/STATE.md +@.planning/workstreams/app/phases/01-foundation/01-CONTEXT.md +@.planning/workstreams/app/phases/01-foundation/01-RESEARCH.md +@.planning/workstreams/app/phases/01-foundation/01-PATTERNS.md + +@src/CMakeLists.txt +@src/lib/gcs_core.hpp +@GNUS-NEO-SWARM/src/genius_elm_chat_completions.h +@GNUS-NEO-SWARM/src/genius_elm_chat_completions.cpp +@GNUS-NEO-SWARM/neoswarm_ffi/src/CMakeLists.txt + + + + +From src/lib/gcs_core.hpp: +```cpp +namespace gcs { +class CoreSession { +public: + struct Config { std::string m_dbPath; }; + explicit CoreSession(Config config); + outcome::result Initialize(); + outcome::result Shutdown(); + bool IsRunning() const; + outcome::result AddBroadcastTopic(const std::string&); + outcome::result AddListenTopic(const std::string&); + outcome::result Put(const std::string& key, const std::string& value); + outcome::result Get(const std::string& key); +}; +} +``` + +The export-macro + extern "C" idiom to copy verbatim (from genius_elm_chat_completions.h:6-22): +```c +#if defined( _WIN32 ) +#if defined( GCS_FFI_EXPORTS ) +#define GCS_FFI_API __declspec( dllexport ) +#else +#define GCS_FFI_API __declspec( dllimport ) +#endif +#else +#define GCS_FFI_API +#endif + +#if defined( __cplusplus ) +#define GCS_FFI_NOEXCEPT noexcept +extern "C" { +#else +#define GCS_FFI_NOEXCEPT +#endif +``` + + + + + + + Task 1: Create src/ffi/CMakeLists.txt + add add_subdirectory(ffi) to src/CMakeLists.txt + src/ffi/CMakeLists.txt, src/CMakeLists.txt + + - GNUS-NEO-SWARM/neoswarm_ffi/src/CMakeLists.txt (SHARED idiom, Android 16k link option) + - GNUS-NEO-SWARM/src/storage/CMakeLists.txt (export-define + linkage pattern) + - src/CMakeLists.txt (file being appended to) + - .planning/workstreams/app/phases/01-foundation/01-PATTERNS.md § "src/ffi/CMakeLists.txt (new — SHARED lib target)" + + + Create src/ffi/CMakeLists.txt (new file) with EXACTLY this content shape (no + cmake_minimum_required, no project() — those belong to the parent build): + + # gcs_ffi — SHARED library exposing the single opaque-handle C API to Flutter (D-01, D-02). + + add_library(gcs_ffi SHARED + gcs_core_ffi.cpp + ) + + set_target_properties(gcs_ffi PROPERTIES + PUBLIC_HEADER gcs_core.h + OUTPUT_NAME "gcs_ffi" + ) + + target_link_libraries(gcs_ffi PRIVATE gcs_core) + target_compile_definitions(gcs_ffi PRIVATE GCS_FFI_EXPORTS) # dllexport macro + + if(ANDROID) + # Support Android 15 16k page size (mirrors neoswarm_ffi) + target_link_options(gcs_ffi PRIVATE "-Wl,-z,max-page-size=16384") + endif() + + Then edit src/CMakeLists.txt: append `add_subdirectory(ffi)` as the last line. This is the + line that Plan 02 deliberately held back so each commit stays buildable. + + PRIVATE linkage on gcs_core is intentional — gcs_ffi is a leaf; nothing links to it from C++. + The GCS_FFI_EXPORTS define is PRIVATE so consumers see dllimport. + + + grep -q "add_library(gcs_ffi SHARED" src/ffi/CMakeLists.txt && grep -q "add_subdirectory(ffi)" src/CMakeLists.txt + + + - src/ffi/CMakeLists.txt exists with the exact add_library, set_target_properties, target_link_libraries, target_compile_definitions, and ANDROID link-options blocks + - src/CMakeLists.txt's last non-empty line is `add_subdirectory(ffi)` + - No cmake_minimum_required or project() in src/ffi/CMakeLists.txt + + CMake wiring complete; configure will pick up the new target once the .h/.cpp exist (Tasks 2-3). + + + + Task 2: Create src/ffi/gcs_core.h (single opaque-handle C API) + src/ffi/gcs_core.h + + - GNUS-NEO-SWARM/src/genius_elm_chat_completions.h (full file — the style template; 103 lines) + - .planning/workstreams/app/phases/01-foundation/01-PATTERNS.md § "src/ffi/gcs_core.h (new — single opaque-handle C API)" + - .planning/workstreams/app/phases/01-foundation/01-CONTEXT.md (D-01) + + + Create src/ffi/gcs_core.h (new file). The header must contain, in order: + + 1. Doxygen file header (\file, \brief, \date, \copyright) + 2. Header guard: `#ifndef GENIUS_COGNITIVE_SYSTEM_GCS_CORE_H` / `#define ... ` + 3. `#include ` and `#include ` (for int64_t — Dart NativePort) + 4. The GCS_FFI_API export-macro block (verbatim from the pattern; the ONLY permitted _WIN32 ifdef) + 5. The GCS_FFI_NOEXCEPT + extern "C" open guard + 6. Forward declaration: `typedef struct GcsSession GcsSession;` (opaque) + 7. Status-code enum or constants (int return; 0 = success, non-zero = error — name them + `GCS_OK`, `GCS_ERROR_GENERIC`, `GCS_ERROR_NOT_IMPLEMENTED`, `GCS_ERROR_INVALID_ARGUMENT`, + `GCS_ERROR_NOT_RUNNING` as constexpr-style #defines or a C enum; planner's discretion, + but pick one form and use it consistently) + 8. Function declarations, each with full Doxygen (\brief, \param[in], \return): + + GCS_FFI_API GcsSession* gcs_init( const char* dbPath ) GCS_FFI_NOEXCEPT; + GCS_FFI_API void gcs_shutdown( GcsSession* session ) GCS_FFI_NOEXCEPT; + GCS_FFI_API int gcs_join_topic( GcsSession* session, const char* topic ) GCS_FFI_NOEXCEPT; + GCS_FFI_API int gcs_publish( GcsSession* session, const char* topic, + const char* utf8Payload ) GCS_FFI_NOEXCEPT; + GCS_FFI_API int gcs_on_message( GcsSession* session, int64_t dartPort ) GCS_FFI_NOEXCEPT; + GCS_FFI_API void gcs_string_free( char* value ) GCS_FFI_NOEXCEPT; + + 9. extern "C" close + #endif + + The Doxygen on gcs_on_message must explicitly state: "Phase 1 stubs this to return + GCS_ERROR_NOT_IMPLEMENTED. Implementation lands in Plan 05 (NativePort spike) — the ABI is + stable so callers can be written now." + + Style notes: + - This is a C header — no `namespace`, no `constexpr`, no templates. C-compatible. + - Allman braces do NOT apply (no function bodies in a header). Spacing rule `if ( x )` does + not apply (no if-statements). The export macro block follows the analog's exact formatting. + - 120-char max line length. + + + grep -cE "GCS_FFI_API.*gcs_(init|shutdown|join_topic|publish|on_message|string_free)" src/ffi/gcs_core.h | grep -q "^6$" + + + - Header guard name is GENIUS_COGNITIVE_SYSTEM_GCS_CORE_H + - All six functions declared with GCS_FFI_API and GCS_FFI_NOEXCEPT + - Opaque `GcsSession` typedef present + - Export-macro _WIN32 block is the ONLY preprocessor platform guard (no other ifdefs except the __cplusplus extern "C" guard and the header guard) + - gcs_on_message Doxygen documents the Phase 1 stub + + C API declared; ffigen can consume this header (dev-loop step, not CI). + + + + Task 3: Create src/ffi/gcs_core_ffi.cpp (FFI thunk over CoreSession) + src/ffi/gcs_core_ffi.cpp + + - GNUS-NEO-SWARM/src/genius_elm_chat_completions.cpp (full file — 227 lines; the implementation template) + - src/ffi/gcs_core.h (from Task 2) + - src/lib/gcs_core.hpp (CoreSession interface from Plan 02) + - .planning/workstreams/app/phases/01-foundation/01-PATTERNS.md § "src/ffi/gcs_core_ffi.cpp (new — FFI thunk)" + + + Create src/ffi/gcs_core_ffi.cpp (new file) implementing the C API. + + Structure (mirroring genius_elm_chat_completions.cpp): + 1. Doxygen file header + 2. Includes: "gcs_core.h", "lib/gcs_core.hpp", , , , + 3. Anonymous namespace with global state: + std::mutex g_mutex; + std::unique_ptr g_session; // Phase 1: single global session + 4. AllocCopy helper (verbatim copy of the analog's lines 28-38; used by future string-returning + APIs — declared now so gcs_string_free has a paired alloc; not used by Phase 1 functions + but kept for ABI stability) + 5. extern "C" block with all six function definitions + + Function behaviors: + - gcs_init(dbPath): lock mutex. If g_session exists, return it (idempotent). Otherwise build + gcs::CoreSession::Config from dbPath (null/empty → use default empty string), construct + CoreSession, call Initialize(). On failure, return nullptr. On success, move into g_session + and return reinterpret_cast(g_session.get()). + - gcs_shutdown(session): lock mutex. If g_session non-null AND + reinterpret_cast(session) == g_session.get(): call Shutdown() then + g_session.reset(). Null or mismatched handle → no-op. + - gcs_join_topic(session, topic): lock mutex. Validate session != null, g_session matches, + topic != null && topic[0] != '\0'. Return GCS_ERROR_INVALID_ARGUMENT on bad input. Call + g_session->AddBroadcastTopic(topic) AND AddListenTopic(topic). Return GCS_OK on success, + GCS_ERROR_GENERIC on failure. (Both calls — "join" from the FFI caller's perspective means + publish + subscribe; matches gossipsub implicit-join semantics per RESEARCH § Smoke Test.) + - gcs_publish(session, topic, utf8Payload): lock mutex. Validate all three. Call + g_session->Put(topic, utf8Payload) — Phase 1 uses Put with the topic as the key (the real + pub/sub message flow lands in Phase 3; this is a substrate smoke call). Return GCS_OK or + GCS_ERROR_GENERIC. Document this in the function's Doxygen. + - gcs_on_message(session, dartPort): lock mutex. Validate session. Return + GCS_ERROR_NOT_IMPLEMENTED (stub per Plan 05 spike; ABI stable). + - gcs_string_free(value): std::free(value). Null-safe (free(nullptr) is defined behavior). + + Every function: `std::lock_guard lock(g_mutex);` as the FIRST statement. Every + function is noexcept via the GCS_FFI_NOEXCEPT macro. No exceptions escape. No new includes + beyond what is listed. No spdlog calls — the C++ core logs internally; the FFI layer is thin. + + Build verification: + cmake --build build/OSX/Debug -j + must succeed AND produce build/OSX/Debug/gcs_src/ffi/libgcs_ffi.dylib (or .so/.dll per platform). + + + cmake --build build/OSX/Debug -j 2>&1 | tee /tmp/gcs_ffi_build.log | tail -5 ; test -f build/OSX/Debug/gcs_src/ffi/libgcs_ffi.dylib && nm -gU build/OSX/Debug/gcs_src/ffi/libgcs_ffi.dylib | grep -cE "_gcs_(init|shutdown|join_topic|publish|on_message|string_free)" | grep -q "^6$" + + + - Build succeeds with zero errors + - libgcs_ffi.dylib (or platform equivalent) exists in the build tree + - All six gcs_* symbols are exported (nm -gU on dylib, dumpbin /exports on Windows) + - Every function takes the global mutex as its first statement + - No exceptions, no OS ifdefs (other than the header's export macro), no magic numbers + (status codes are named constants from gcs_core.h) + + gcs_ffi builds and exports a stable C ABI; ready for FFI smoke test (Plan 04) and Dart spike (Plan 05). + + + + + +- `cmake --build build/OSX/Debug -j` green +- `nm -gU build/OSX/Debug/gcs_src/ffi/libgcs_ffi.dylib | grep gcs_` shows all six exports +- Plan 04's test_gcs_ffi.cpp will link against this library and exercise the API + + + +gcs_ffi exists as a SHARED library with the single opaque-handle C API, built on OSX Debug, +matching the D-01/D-02 contract and ready for Dart loading via DynamicLibrary.open. + + + +Create `.planning/workstreams/app/phases/01-foundation/01-03-SUMMARY.md` when done. + diff --git a/.planning/workstreams/app/phases/01-foundation/01-04-PLAN.md b/.planning/workstreams/app/phases/01-foundation/01-04-PLAN.md new file mode 100644 index 0000000..b1793a1 --- /dev/null +++ b/.planning/workstreams/app/phases/01-foundation/01-04-PLAN.md @@ -0,0 +1,322 @@ +--- +phase: 01-foundation +plan: 04 +type: execute +wave: 3 +depends_on: ["01-01", "01-02"] +files_modified: + - test/CMakeLists.txt + - test/test_wait_condition.hpp + - test/test_gcs_core_smoke.cpp + - test/test_gcs_ffi.cpp +autonomous: true +requirements: [CORE-05] +must_haves: + truths: + - "test/test_gcs_core_smoke.cpp proves CORE-05 substrate: GcsGlobalDb initializes via injected GossipPubSub (port 0), AddBroadcastTopic + AddListenTopic succeed on 'gcs/chat/smoke-test', and Put→Get round-trips" + - "test/test_gcs_ffi.cpp proves FFI surface under OPTION C semantics: gcs_init returns nullptr gracefully (no crash, no exception) when the GeniusSDK node is unavailable; argument-validation / version / shutdown-safe-on-null tests do NOT require a live node; full init-success proof is deferred to Plan 01-05's Dart spike" + - "All tests use WaitForCondition (condition_variable polling) — zero sleep_for / sleep_until anywhere in test/" + - "ctest --test-dir build/OSX/Debug -R gcs --output-on-failure is green" + artifacts: + - path: "test/CMakeLists.txt" + provides: "GTest discovery + gcs_test macro + registration of both smoke tests" + contains: "gcs_test(test_gcs_core_smoke" + - path: "test/test_wait_condition.hpp" + provides: "WaitForCondition template (copied verbatim from neoswarm analog)" + contains: "WaitForCondition" + - path: "test/test_gcs_core_smoke.cpp" + provides: "CORE-05 lifecycle + topic + Put/Get round-trip test" + contains: "gcs/chat/smoke-test" + - path: "test/test_gcs_ffi.cpp" + provides: "FFI init/echo/shutdown smoke test" + contains: "gcs_init" + key_links: + - from: "test/test_gcs_core_smoke.cpp" + to: "gcs_core" + via: "target_link_libraries via gcs_test macro" + pattern: "gcs_core;neoswarm_storage" + - from: "test/test_gcs_ffi.cpp" + to: "gcs_ffi" + via: "target_link_libraries via gcs_test macro" + pattern: "gcs_ffi" +--- + + +Stand up the GCS-root `test/` tree with two smoke-test binaries that prove Phase 1's success +criteria 2, 3, and 4 from ROADMAP.md (GlobalDB init + pub/sub round-trip; FFI init + echo; +GossipSub topic create/join). + +Purpose: These are the objective, CI-runnable proofs that the Phase 1 wiring works. Without them +the phase has no verification story and `/gsd:verify-work` cannot pass. + +Output: `test/` directory with 4 files; `ctest -R gcs` green locally on OSX; CI runs these in +Plan 06. + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/workstreams/app/STATE.md +@.planning/workstreams/app/phases/01-foundation/01-CONTEXT.md +@.planning/workstreams/app/phases/01-foundation/01-RESEARCH.md +@.planning/workstreams/app/phases/01-foundation/01-PATTERNS.md + +@GNUS-NEO-SWARM/test/CMakeLists.txt +@GNUS-NEO-SWARM/test/storage/test_gcs_global_db.cpp +@GNUS-NEO-SWARM/test/ffi/test_genius_elm_ffi.cpp +@src/lib/gcs_core.hpp +@src/ffi/gcs_core.h + + + +From src/lib/gcs_core.hpp: +```cpp +outcome::result Initialize(std::shared_ptr pubsub); +``` + +The pre-existing hook that registers test/ into the parent build: + build/CommonBuildParameters.cmake lines 146-151 already do + add_subdirectory(${PROJECT_ROOT}/test ...) + when BUILD_TESTING is on. No root CMakeLists change is needed. + +The GossipPubSub bring-up recipe (from GNUS-NEO-SWARM/test/storage/test_gcs_global_db.cpp:140-158): + - KeyPairFileStorage keyStore(keyDir) → GetKeyPair() + - make_shared(keyPair) + - pubsub->Start(0, {}, "0.0.0.0", {}).get() // port 0 = ephemeral + +The mandatory soralog one-time setup (from same file, lines 46-58 + 108-117). Omitting this +crashes GossipPubSub construction with "Logging system is not ready" (RESEARCH Pitfall 2). + + + + + + + Task 1: Create test/CMakeLists.txt + test/test_wait_condition.hpp (test scaffolding) + test/CMakeLists.txt, test/test_wait_condition.hpp + + - GNUS-NEO-SWARM/test/CMakeLists.txt (the GTest discovery + helper-macro pattern; 89 lines) + - GNUS-NEO-SWARM/test/storage/test_gcs_global_db.cpp lines 33-91 (the WaitForCondition implementation to extract) + - .planning/workstreams/app/phases/01-foundation/01-PATTERNS.md §§ "test/CMakeLists.txt" and "test/test_wait_condition.hpp" + + + Create two files. + + FILE 1: test/CMakeLists.txt + - Copy the GTest-discovery block from the neoswarm analog verbatim (find_package(GTest QUIET) + with the THIRDPARTY_BUILD_DIR fallback using find_path/find_library; creates GTest::GTest + and GTest::Main imported targets; WARNING + return() if not found). + - Rename the helper macro from `neoswarm_test` to `gcs_test`. + - Drop the SUPERGENIUS_TEST_DATA_DIR define (not needed). + - At the bottom, register the two tests: + gcs_test(test_gcs_core_smoke test_gcs_core_smoke.cpp "gcs_core;neoswarm_storage;sgns::crdt_globaldb") + gcs_test(test_gcs_ffi test_gcs_ffi.cpp "gcs_ffi;gcs_core") + - Call enable_testing() before the macro definition. + + FILE 2: test/test_wait_condition.hpp + - Copy the file content from PATTERNS.md § "test/test_wait_condition.hpp" verbatim. It is a + complete, ready-to-use header (Doxygen header, header guard + GCS_TEST_WAIT_CONDITION_HPP, namespace gcs::test, kWaitTimeout=25s, kPollInterval=10ms, + inline WaitForCondition function). Do not modify. + + No CMake configure verification yet — the smoke test .cpp files don't exist until Task 2. + Defer build verification to Task 2/3. + + + test -f test/CMakeLists.txt && test -f test/test_wait_condition.hpp && grep -q "gcs_test(test_gcs_core_smoke" test/CMakeLists.txt && grep -q "WaitForCondition" test/test_wait_condition.hpp + + + - test/CMakeLists.txt exists with the gcs_test macro and two test registrations + - test/test_wait_condition.hpp exists with kWaitTimeout{25000}, kPollInterval{10}, and an inline WaitForCondition function + - No mention of neoswarm_test or SUPERGENIUS_TEST_DATA_DIR in the new CMakeLists + + Test scaffolding in place; ready for the actual test bodies. + + + + Task 2: Create test/test_gcs_core_smoke.cpp (CORE-05 substrate proof) + test/test_gcs_core_smoke.cpp + + - GNUS-NEO-SWARM/test/storage/test_gcs_global_db.cpp (FULL 249 lines — the structural template) + - test/test_wait_condition.hpp (from Task 1) + - src/lib/gcs_core.hpp (CoreSession interface from Plan 02) + - .planning/workstreams/app/phases/01-foundation/01-PATTERNS.md § "test/test_gcs_core_smoke.cpp" + + + Create test/test_gcs_core_smoke.cpp. Mirror the neoswarm analog's structure but exercise + gcs::CoreSession (not GcsGlobalDb directly). + + File structure: + 1. Doxygen header + 2. Includes (in this order): "lib/gcs_core.hpp", "test_wait_condition.hpp", standard headers, + gtest, "crdt/globaldb/keypair_file_storage.hpp", "ipfs_pubsub/gossip_pubsub.hpp", + libp2p log configurators, soralog YAML configurator + 3. Anonymous namespace constants: kListenIp = "0.0.0.0", kLoggingYaml (minimal console sink, + error level, group name "gcs_core_smoke_test" with libp2p+Gossip children), and + kSmokeTopic = "gcs/chat/smoke-test" + 4. GcsCoreSmokeTest fixture class inheriting ::testing::Test with: + - static void SetUpTestSuite() — the soralog YAML block (MANDATORY, verbatim from analog) + - void SetUp() override — unique temp dir per test (steady_clock salt) + - void TearDown() override — remove temp dir via error_code overload + - helper: MakeStartedPubSub(keyDir) returning shared_ptr on port 0 + - member: std::string m_tempPath + 5. Two TEST_F cases: + + TEST_F(GcsCoreSmokeTest, LifecycleWithInjectedPubSub) + - pubsub = MakeStartedPubSub(m_tempPath + "/key"); ASSERT non-null + - CoreSession::Config cfg; cfg.m_dbPath = m_tempPath + "/db" + - CoreSession session(cfg) + - ASSERT Initialize(pubsub).has_value() + - EXPECT IsRunning() == true + - WaitForCondition(dbPath exists) — handles lazy DB dir creation + - session.Shutdown() + - EXPECT IsRunning() == false + - pubsub->Stop() + + TEST_F(GcsCoreSmokeTest, CrdtPutGetRoundTripsOnGcsChatTopic) + - Same setup as above + - ASSERT session.AddBroadcastTopic(kSmokeTopic).has_value() + - ASSERT session.AddListenTopic(kSmokeTopic).has_value() + - session.Put("smoke-key", "smoke-value") — ASSERT has_value() + - auto result = session.Get("smoke-key") — ASSERT has_value(), EXPECT_EQ result.value(), + "smoke-value" + - Shutdown + Stop + + Test 2 covers ROADMAP success criteria 2 (GlobalDB init + pub/sub round-trip) and 4 + (GossipSub topic create/join from C++ core). Topic name uses the gcs/chat/ convention + per CONTEXT canonical_refs. + + Strict rules: NO std::this_thread::sleep_for, NO std::this_thread::sleep_until. Use only + gcs::test::WaitForCondition. Allman braces, space inside parens, m_ member prefix, + kCamelCase constants, 120-char max, C++17. + + + grep -c "sleep_for\|sleep_until" test/test_gcs_core_smoke.cpp | grep -q "^0$" && cmake --build build/OSX/Debug -j 2>&1 | tail -10 | grep -E "error|FAILED" ; ctest --test-dir build/OSX/Debug -R test_gcs_core_smoke --output-on-failure 2>&1 | tail -15 | grep -E "Passed|Failed" + + + - grep -c "sleep_for\|sleep_until" returns 0 + - Build succeeds + - ctest -R test_gcs_core_smoke reports 2/2 tests passed + - Both tests use WaitForCondition at least once + - File contains kSmokeTopic = "gcs/chat/smoke-test" + + CORE-05 substrate smoke test green on OSX Debug. + + + + Task 3: Create test/test_gcs_ffi.cpp (FFI init/echo/shutdown) + test/test_gcs_ffi.cpp + + - GNUS-NEO-SWARM/test/ffi/test_genius_elm_ffi.cpp (full file — the FFI test template) + - src/ffi/gcs_core.h (the C API from Plan 03) + - .planning/workstreams/app/phases/01-foundation/01-PATTERNS.md § "test/test_gcs_ffi.cpp" + + + Create test/test_gcs_ffi.cpp. This test exercises the gcs_ffi C ABI exactly as Dart will + (link gcs_ffi only — NOT gcs_core directly). + + Structure: + 1. Doxygen header + 2. Includes: "ffi/gcs_core.h" and gtest + 3. GcsFFI fixture with TearDown that calls gcs_shutdown(m_handle) if m_handle non-null — + mandatory to avoid pthread abort at process exit (the analog's FIX-01 comment explains + why: GcsGlobalDb's destructor touches asio::io_context internals that must be torn down + before GTest's static destruction) + 4. Member: GcsSession* m_handle = nullptr + + Tests (all use the fixture). RESOLVED AT PLANNING TIME (2026-08-15) — OPTION C is locked + in. gcs_init's no-arg Initialize path calls GeniusSDKGetNode(), which WILL return nullptr + in the test environment; gcs_init must therefore return nullptr gracefully (no crash, no + exception). Tests assert that contract directly. Full init-success proof is Plan 01-05's + Dart spike responsibility. There is NO test in this plan that touches gcs_on_message — + that symbol is Plan 01-05's responsibility end-to-end. + + TEST_F(GcsFFI, InitWithoutNodeReturnsNullptrGracefully) + m_handle = gcs_init(nullptr); + EXPECT_EQ(m_handle, nullptr); // no crash, no exception — graceful null + // Do NOT call gcs_shutdown on a null handle here — ShutdownNullptrIsSafeNoOp covers that. + + TEST_F(GcsFFI, InitWithExplicitPathWithoutNodeReturnsNullptrGracefully) + Build a temp path under std::filesystem::temp_directory_path() with a unique salt; + m_handle = gcs_init(path.c_str()); + EXPECT_EQ(m_handle, nullptr); // explicit path does not change the option-C contract + // No filesystem cleanup needed — init failed before any directory was created. + + TEST_F(GcsFFI, RepeatedInitCallsWithoutNodeAllReturnNullptr) + GcsSession* h1 = gcs_init(nullptr); + GcsSession* h2 = gcs_init(nullptr); + GcsSession* h3 = gcs_init(nullptr); + EXPECT_EQ(h1, nullptr); + EXPECT_EQ(h2, nullptr); + EXPECT_EQ(h3, nullptr); + // m_handle stays nullptr — TearDown's conditional shutdown is a no-op. + + TEST_F(GcsFFI, StringFreeNullptrDoesNotCrash) + gcs_string_free(nullptr); + SUCCEED(); + + TEST_F(GcsFFI, ShutdownNullptrIsSafeNoOp) + gcs_shutdown(nullptr); + SUCCEED(); + + TEST_F(GcsFFI, JoinTopicValidatesArgumentsWithoutNode) + // Argument validation must fire BEFORE the session is dereferenced, so these calls + // are safe even though gcs_init returned nullptr under option C. + EXPECT_EQ(gcs_join_topic(nullptr, "gcs/chat/x"), GCS_ERROR_INVALID_ARGUMENT); + m_handle = gcs_init(nullptr); // returns nullptr under option C + EXPECT_EQ(gcs_join_topic(m_handle, nullptr), GCS_ERROR_INVALID_ARGUMENT); + EXPECT_EQ(gcs_join_topic(m_handle, ""), GCS_ERROR_INVALID_ARGUMENT); + + Explicitly REMOVED from this plan (moved to Plan 01-05): + - Any test calling gcs_on_message (including the previously-listed + OnMessageReturnsNotImplemented). Plan 01-05 owns all on_message tests in their final + form once the real implementation lands — there is no value in 01-04 asserting the + stub contract that 01-05 immediately replaces. + - Any EXPECT_NE(m_handle, nullptr) init-success assertion. Full init success requires + a live GeniusSDK node, which is not available in the ctest environment. Plan 01-05's + Dart spike exercises that path. + + Add a Doxygen comment block at the top of the file stating: "Phase 1 option C: gcs_init + returns nullptr when the GeniusSDK node is unavailable (test/CI envs). Full init-success + proof lives in Plan 01-05's Dart spike. This file intentionally contains NO gcs_on_message + tests — that symbol is owned end-to-end by Plan 01-05." + + Style: same as Task 2 (no sleep_for, Allman, m_ prefix, kCamelCase). + + + grep -c "sleep_for\|sleep_until" test/test_gcs_ffi.cpp | grep -q "^0$" && cmake --build build/OSX/Debug -j 2>&1 | tail -5 | grep -E "error|FAILED" ; ctest --test-dir build/OSX/Debug -R test_gcs_ffi --output-on-failure 2>&1 | tail -15 | grep -E "Passed|Failed|tests passed" + + + - All 6 tests pass (gtest) + - No test in the file calls gcs_on_message (grep confirms zero hits) + - No test in the file asserts EXPECT_NE on a gcs_init return (option C: nullptr is the expected value) + - TearDown is present and calls gcs_shutdown conditionally (safe on null m_handle) + - No sleep_for / sleep_until + - File's top-of-file Doxygen comment states the option-C contract and the Plan 01-05 ownership of gcs_on_message tests + + FFI surface smoke-tested; ABI proven loadable and callable from C++ test code. + + + + + +- `ctest --test-dir build/OSX/Debug -R gcs --output-on-failure` — all GCS tests pass +- `grep -r "sleep_for\|sleep_until" test/` returns zero hits +- `ls build/OSX/Debug/gcs_src/test/` shows both test binaries + + + +- ROADMAP success criterion 2 (GlobalDB CRDT init + pub/sub round-trip): green +- ROADMAP success criterion 4 (GossipSub topic create/join): green +- FFI boundary proven null-safe and argument-validated under option C (no live GeniusSDK node) +- test/ tree is ready to be picked up by CI in Plan 06 +- ROADMAP success criterion 3 ("Flutter app can call into C++ via FFI and receive a response") + is NOT claimed by this plan — Plan 01-05's Dart smoke test owns it + + + +Create `.planning/workstreams/app/phases/01-foundation/01-04-SUMMARY.md` when done. + diff --git a/.planning/workstreams/app/phases/01-foundation/01-05-PLAN.md b/.planning/workstreams/app/phases/01-foundation/01-05-PLAN.md new file mode 100644 index 0000000..9f8681c --- /dev/null +++ b/.planning/workstreams/app/phases/01-foundation/01-05-PLAN.md @@ -0,0 +1,361 @@ +--- +phase: 01-foundation +plan: 05 +type: execute +wave: 4 +depends_on: ["01-03", "01-04"] +files_modified: + - src/ffi/dart_native_api.h + - src/ffi/dart_api_dl.c + - src/ffi/gcs_core.h + - src/ffi/gcs_core_ffi.cpp + - src/ffi/CMakeLists.txt + - src/app/ffigen.yaml + - src/app/lib/gcs_bindings_generated.dart + - src/app/test/gcs_native_port_smoke_test.dart +autonomous: false +requirements: [CORE-05] +user_setup: + - service: flutter_sdk + why: "Dart SDK / Flutter engine ships dart_native_api.h and provides the runtime that resolves Dart_PostCObject at load time" + env_vars: + - name: FLUTTER_ROOT + source: "Local Flutter installation path (e.g. `flutter --version` shows it); needed to locate include/third_party/dart/runtime/include/dart_native_api.h for vendoring" +must_haves: + truths: + - "gcs_on_message is no longer a stub — it registers a Dart NativePort on the session" + - "A minimal Dart test (run via `flutter test` or `dart test`) loads libgcs_ffi, calls gcs_init, registers a ReceivePort via gcs_on_message, publishes via gcs_publish, and observes a posted message on the ReceivePort" + - "ffigen.yaml points at the REAL src/ffi/gcs_core.h (NOT the stale genius_slm_chat_c.h path)" + - "Dart-side never polls; messages arrive via ReceivePort (D-05 honored)" + artifacts: + - path: "src/ffi/dart_native_api.h" + provides: "Vendored Dart VM C API header (Dart_PostCObjectType, Dart_CObject)" + contains: "Dart_PostCObjectType" + - path: "src/ffi/dart_api_dl.c" + provides: "Vendored Dart API_DL initializer (Dart_InitializeApiDL)" + contains: "Dart_InitializeApiDL" + - path: "src/app/ffigen.yaml" + provides: "ffigen config targeting src/ffi/gcs_core.h" + contains: "gcs_core.h" + - path: "src/app/lib/gcs_bindings_generated.dart" + provides: "ffigen-generated Dart bindings for the gcs_ffi API" + contains: "gcs_on_message" + - path: "src/app/test/gcs_native_port_smoke_test.dart" + provides: "Dart-side smoke test exercising NativePort round-trip" + contains: "ReceivePort" + key_links: + - from: "src/ffi/gcs_core_ffi.cpp" + to: "Dart_PostCObject" + via: "Dart_InitializeApiDL at load + Dart_PostCObject on message receipt" + pattern: "Dart_PostCObject" + - from: "src/app/test/gcs_native_port_smoke_test.dart" + to: "libgcs_ffi.dylib" + via: "DynamicLibrary.open + ffigen bindings" + pattern: "DynamicLibrary.open" +--- + + +Close the Dart NativePort gap identified in RESEARCH Open Question 1: implement the standard +Dart API_DL pattern (`Dart_InitializeApiDL` + `Dart_PostCObject` + `ReceivePort`) inside gcs_ffi, +generate Dart bindings via ffigen, and prove the round-trip with a Dart-side smoke test. + +Purpose: D-05 locks "push, don't poll" — network data flows C++ → Dart via NativePort. Without +this plan, the FFI surface is one-directional (Dart → C++ only) and Phase 3's messaging cannot +deliver incoming messages to Flutter. + +This is the phase's only genuine "no in-repo precedent" work. The spike validates: + 1. Vendoring `dart_native_api.h` + `dart_api_dl.c` into src/ffi/ (the package:ffigen-documented + approach) — vs. weak-linkage declare. + 2. Initializing the API_DL table at library load. + 3. Posting a string `Dart_CObject` to a registered port when a CRDT update arrives. + +Output: working native→Dart callback path; Phase 3+ messaging plugs into it. + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/workstreams/app/STATE.md +@.planning/workstreams/app/phases/01-foundation/01-CONTEXT.md +@.planning/workstreams/app/phases/01-foundation/01-RESEARCH.md +@.planning/workstreams/app/phases/01-foundation/01-PATTERNS.md + +@src/ffi/gcs_core.h +@src/ffi/gcs_core_ffi.cpp +@src/ffi/CMakeLists.txt +@GNUS-NEO-SWARM/neoswarm_ffi/ffigen.yaml +@src/app/pubspec.yaml + + + + +Standard include strategy (the "vendored" pattern documented in package:ffigen): + - Copy `dart_native_api.h` + `dart_api_dl.c` + `dart_api_dl.h` + `dart_version.h` from + `$FLUTTER_ROOT/bin/cache/dart-sdk/include/` into `src/ffi/`. + - In gcs_core_ffi.cpp's translation unit, call `Dart_InitializeApiDL(NativeMajorVersion, ...)` + once at first gcs_init — or via a library-load hook if available. + -dart_api_dl.c provides the Dart_PostCObject symbol indirection. + +C-side posting shape: +```c +Dart_CObject message; +message.type = Dart_CObject_kString; +message.value.as_string = const_cast(utf8_payload); +Dart_PostCObject(port, &message); +``` + +Dart-side receive shape: +```dart +final receivePort = ReceivePort(); +bindings.gcs_on_message(handle, receivePort.sendPort.nativePort); +receivePort.listen((msg) { /* msg is String */ }); +``` + +ffigen mapping: `int64_t` ↔ Dart `int`, `GcsSession*` ↔ `Pointer` (opaque struct). + + + + + + + Task 1: Vendor Dart API_DL headers/source into src/ffi/ and wire into CMake + src/ffi/dart_native_api.h, src/ffi/dart_api_dl.h, src/ffi/dart_api_dl.c, src/ffi/dart_version.h, src/ffi/CMakeLists.txt + + - $FLUTTER_ROOT/bin/cache/dart-sdk/include/ (locate the four Dart API_DL files) + - src/ffi/CMakeLists.txt (file being modified) + - .planning/workstreams/app/phases/01-foundation/01-RESEARCH.md § "FFI Callback Mechanism" + Assumption A1 + + + Copy four files from the local Dart SDK into src/ffi/: + - dart_native_api.h + - dart_api_dl.h + - dart_api_dl.c + - dart_version.h + - internal/dart_api_dl_impl.h (if present; some SDK versions split internals) + + Locate them via: + flutter --version # find FLUTTER_ROOT + ls $FLUTTER_ROOT/bin/cache/dart-sdk/include/ + + If the SDK layout differs (newer Flutter versions move headers), adapt — but do NOT rename + the vendored files; keep their canonical names so future SDK bumps are a clean copy-over. + + Edit src/ffi/CMakeLists.txt to: + - Add dart_api_dl.c to the gcs_ffi sources + - Add src/ffi to gcs_ffi's PRIVATE include dirs so "dart_native_api.h" resolves + + Add a Doxygen header to dart_api_dl.c's FIRST line noting it is vendored from the Dart SDK, + the SDK version it came from, and the date — so future maintainers know to refresh it. + (Append as a comment; do not modify the file's logic.) + + Verify the build still succeeds: `cmake --build build/OSX/Debug -j`. + + + test -f src/ffi/dart_native_api.h && test -f src/ffi/dart_api_dl.c && grep -q "dart_api_dl.c" src/ffi/CMakeLists.txt && cmake --build build/OSX/Debug -j 2>&1 | tail -5 | grep -E "error|FAILED" ; echo "build-ok" + + + - All four vendored files present in src/ffi/ + - dart_api_dl.c appears in the gcs_ffi add_library sources list + - Build still green (no new errors introduced) + + Dart API_DL machinery available to gcs_ffi. + + + + Task 2: Implement gcs_on_message + message posting in gcs_core_ffi.cpp + src/ffi/gcs_core_ffi.cpp, src/ffi/gcs_core.h + + - src/ffi/gcs_core_ffi.cpp (current state — gcs_on_message is a stub returning GCS_ERROR_NOT_IMPLEMENTED) + - src/ffi/gcs_core.h (update gcs_on_message Doxygen to remove the stub note) + - src/ffi/dart_native_api.h (Dart_CObject layout, Dart_PostCObjectType signature) + - src/lib/gcs_core.hpp (CoreSession API; identify where incoming-message notifications will originate — Phase 1 has no incoming messages yet, so the post path is exercised via a test hook) + + + Replace the gcs_on_message stub with a real implementation. + + Additions to the anonymous namespace in gcs_core_ffi.cpp: + - `std::atomic g_dartPort{ 0 };` — registered Dart port (0 = unregistered) + - `std::atomic g_apiDlInitialized{ false };` + + At the top of gcs_init, after taking the mutex: + - If !g_apiDlInitialized.exchange(true): call Dart_InitializeApiDL(nullptr) — the actual + signature takes a `void* data` (per dart_api_dl.h). Read dart_api_dl.h for the exact + signature and pass nullptr. If it returns non-zero, log via spdlog::error and continue + (do not fail init — the FFI is still usable for non-callback calls). + + Replace gcs_on_message: + - Lock mutex. Validate session != nullptr and g_session matches. + - Store dartPort in g_dartPort. + - Return GCS_OK. + + Add a private helper (still in anonymous namespace): + - `void PostToDart(const char* utf8)` — reads g_dartPort; if non-zero, builds a + Dart_CObject of type Dart_CObject_kString with the payload and calls Dart_PostCObject. + Ignore the return (closed port is safe per Pitfall 6). + + Hook the helper into gcs_publish for Phase 1 verification: + - After g_session->Put(topic, utf8Payload) succeeds, call PostToDart(utf8Payload). + - Document in the Doxygen: "Phase 1 echoes the published payload back to the registered + Dart port for round-trip verification. Phase 3 will replace this with a real CRDT-update + subscription that posts incoming remote messages." + + Update gcs_shutdown: + - Set g_dartPort = 0 BEFORE tearing down g_session (Pitfall 6 — unregister before free). + + Update gcs_core.h: + - Remove the "stubs to return GCS_ERROR_NOT_IMPLEMENTED" sentence from gcs_on_message's + Doxygen. Replace with the real behavior contract. + - Keep the function signature unchanged (ABI stable). + + Build verification: cmake --build build/OSX/Debug -j must succeed; all Plan 04 ctest cases + must still pass. Plan 01-04 intentionally ships with NO gcs_on_message tests (per option C + + revision 2026-08-15, all on_message tests live in this plan). + + Add a new test to test/test_gcs_ffi.cpp IN THIS PLAN (this is the file 01-04 created; 01-05 + extends it — file ownership moves with the symbol): + + TEST_F(GcsFFI, OnMessageRegistersPortAndReturnsOk) + // gcs_init returns nullptr under option C (no live node in ctest env), so this test + // passes a synthetic non-null session pointer ONLY to exercise argument validation. + // The full live-handle path is covered by the Dart smoke test (Task 4). + EXPECT_EQ(gcs_on_message(nullptr, 0), GCS_ERROR_INVALID_ARGUMENT); + + TEST_F(GcsFFI, OnMessageValidatesArguments) + // Null session → INVALID_ARGUMENT; verifies the new implementation did not regress + // argument validation when the stub was replaced. + EXPECT_EQ(gcs_on_message(nullptr, /*dartPort=*/1234), GCS_ERROR_INVALID_ARGUMENT); + + These two tests are the FINAL on_message tests for Phase 1. Plan 01-04 must not reference + gcs_on_message at all (revised 2026-08-15). + + + cmake --build build/OSX/Debug -j 2>&1 | tail -5 | grep -E "error|FAILED" ; ctest --test-dir build/OSX/Debug -R gcs --output-on-failure 2>&1 | tail -10 | grep -E "tests passed|Passed|Failed" + + + - gcs_on_message no longer returns GCS_ERROR_NOT_IMPLEMENTED on valid input + - gcs_publish triggers PostToDart with the payload (echo for Phase 1 verification) + - gcs_shutdown clears g_dartPort before teardown + - Two new on_message tests added to test/test_gcs_ffi.cpp (argument validation; live-handle + path is exercised by the Dart smoke test in Task 4) + - All Plan 01-04 ctest cases still green (01-04 has no on_message tests to update) + + Native→Dart post path implemented; ready for the Dart-side smoke test. + + + + Task 3: Create src/app/ffigen.yaml and generate Dart bindings + src/app/ffigen.yaml, src/app/lib/gcs_bindings_generated.dart + + - GNUS-NEO-SWARM/neoswarm_ffi/ffigen.yaml (the ffigen config template — NOTE: its entry-point + header path is stale; do NOT copy that path) + - src/ffi/gcs_core.h (the real entry point) + - src/app/pubspec.yaml (verify ffigen is in dev_dependencies; add `ffigen: ^11.0.0` if missing) + + + Create src/app/ffigen.yaml pointing at the REAL header (per Pitfall 4 — do not repeat the + neoswarm_ffi staleness bug): + + name: GcsBindings + description: Dart bindings for the gcs_ffi C API + output: lib/gcs_bindings_generated.dart + headers: + entry-points: + - '../ffi/gcs_core.h' + comments: + style: any + length: full + + The relative path '../ffi/gcs_core.h' is relative to the ffigen.yaml location (src/app/). + Verify it resolves: from src/app/, `ls ../ffi/gcs_core.h` must exist. + + Check src/app/pubspec.yaml for `ffigen` under dev_dependencies. If absent, add: + dev_dependencies: + ffigen: ^11.0.0 + Then `dart pub get` in src/app/. + + Generate bindings: + cd src/app && dart run ffigen --config ffigen.yaml + + Inspect the generated file briefly: it should contain a class wrapping the six gcs_* + functions and a `GcsSession` opaque struct. + + Do NOT commit generated bindings to .gitignore — they should be tracked so Dart consumers + don't need ffigen locally. (Confirm with user if uncertain — project convention unclear.) + + + test -f src/app/ffigen.yaml && grep -q "gcs_core.h" src/app/ffigen.yaml && test -f src/app/lib/gcs_bindings_generated.dart && grep -c "gcs_init\|gcs_shutdown\|gcs_on_message" src/app/lib/gcs_bindings_generated.dart | grep -q "^[3-9]\|^[1-9][0-9]" + + + - ffigen.yaml's entry-points contains '../ffi/gcs_core.h' (resolves to src/ffi/gcs_core.h) + - gcs_bindings_generated.dart exists and references gcs_init, gcs_shutdown, gcs_on_message + - `dart pub get` succeeds in src/app/ + - No mention of genius_slm_chat_c.h anywhere + + Dart bindings generated and committed. + + + + Task 4: Dart-side NativePort smoke test (manual run — requires Flutter runtime) + src/app/test/gcs_native_port_smoke_test.dart + + A Dart test that: + 1. Loads libgcs_ffi via DynamicLibrary.open (path resolved relative to the build tree: + build/OSX/Debug/gcs_src/ffi/libgcs_ffi.dylib on OSX). + 2. Looks up the six gcs_* symbols via the generated bindings. + 3. Calls gcs_init(null) — EXPECTED to return a non-null Pointer if CoreSession's + Initialize succeeds, OR null if GeniusSDK is unavailable in the test env (acceptable per + Plan 04 option C). If init returns null, the test SKIPS with a logged note. + 4. Creates a ReceivePort, registers it via gcs_on_message(handle, port.nativePort). + 5. Calls gcs_publish(handle, "gcs/chat/smoke-test", "hello-from-dart"). + 6. Awaits a message on the ReceivePort with a 5-second timeout. + 7. Asserts the received string equals "hello-from-dart" (the Phase 1 echo). + 8. Calls gcs_shutdown(handle). + + + Run from the repo root: + + cd src/app && flutter test test/gcs_native_port_smoke_test.dart + + Expected output: "1 test passed" (or "+1: ... passed"). If the test skips due to null init, + that is ACCEPTABLE for Phase 1 — the spike's purpose is to prove the wiring is correct, not + that production init always succeeds in the test env. Document the outcome in the plan summary. + + If the test fails with "Dart_PostCObject symbol not found" or similar: the API_DL + initialization in Task 2 is not completing. Check the stderr for the Dart_InitializeApiDL + return code. + + Type "approved" with the test output pasted, or "skipped: " if init returned null in the test env, or describe issues. + + + + + +- Build green: `cmake --build build/OSX/Debug -j` +- Existing ctest suite green: `ctest --test-dir build/OSX/Debug -R gcs --output-on-failure` +- Dart bindings generated and committed +- Dart smoke suite passes: the FFI entrypoints that do NOT require a live GeniusSDK node + (version/echo/argument-validation + callback registration wiring) run and pass; only the + live-node round-trip leg may carry a `skip:` marker when the SDK is absent in the test env. + Any other skip or failure blocks the plan. + + + +- D-05's "push, don't poll" mechanism is implemented and validated +- gcs_ffi's C ABI now includes a working callback registration +- Phase 3 messaging has a proven native→Dart channel to plug into +- ROADMAP success criterion 3 ("Flutter app can call into C++ via FFI and receive a response") + is satisfied by a real Dart→C++ call/response round-trip through any functioning FFI + entrypoint. Concretely, the Plan 01-05 Dart smoke test passes by exercising the FFI + functions that do NOT require a live GeniusSDK node — version/echo/argument-validation + calls plus callback-registration wiring — and the pass condition for this plan is the + Dart test suite passing. A `skip:` marker is permitted ONLY for the live-node round-trip + leg when GeniusSDK is unavailable in the test environment; the rest of the suite must + run and pass. There is no "partial pass" outcome. + + + +Create `.planning/workstreams/app/phases/01-foundation/01-05-SUMMARY.md` when done. + diff --git a/.planning/workstreams/app/phases/01-foundation/01-06-PLAN.md b/.planning/workstreams/app/phases/01-foundation/01-06-PLAN.md new file mode 100644 index 0000000..29d5141 --- /dev/null +++ b/.planning/workstreams/app/phases/01-foundation/01-06-PLAN.md @@ -0,0 +1,285 @@ +--- +phase: 01-foundation +plan: 06 +type: execute +wave: 5 +depends_on: ["01-03", "01-04"] +files_modified: + - .github/workflows/cmake.yml +autonomous: false +requirements: [CORE-05] +user_setup: + - service: github_selfhosted_runners + why: "Workflow picks self-hosted runners via GNUS_TOKEN_1 (org+repo runner merge in resolve-runners job)" + env_vars: + - name: GNUS_TOKEN_1 + source: "GitHub repo Settings -> Secrets and variables -> Actions (must already exist; copied from SuperGenius/GeniusSDK convention)" + dashboard_config: + - task: "Confirm self-hosted runners with labels sg-ubuntu-linux, sg-arm-linux, SG-WIN11, gv-OSX-Large are registered to GeniusVentures/GeniusCogntiveSystem (or to the org)" + location: "GitHub repo Settings -> Actions -> Runners" +must_haves: + truths: + - ".github/workflows/cmake.yml exists at GCS repo root and triggers on push to develop+main, PRs to develop+main, and workflow_dispatch" + - "Workflow downloads three prebuilt dependency releases (thirdparty, SuperGenius, GeniusSDK) — never builds them from source" + - "Matrix covers all 5 platforms (Android arm64-v8a+armeabi-v7a, iOS, OSX, Linux x86_64+aarch64, Windows) × Debug/Release" + - "ctest runs on Linux, OSX, Windows Release; NOT on Android/iOS (cross-compiles)" + - "zkLLVM has no explicit CI step (D-09) — cmaketemplate's auto-download handles the headers-only consumption" + artifacts: + - path: ".github/workflows/cmake.yml" + provides: "Complete CI workflow (resolve-runners + build/test/release matrix)" + contains: "resolve-runners" + key_links: + - from: ".github/workflows/cmake.yml" + to: "GeniusVentures/thirdparty + GeniusVentures/SuperGenius + GeniusVentures/GeniusSDK releases" + via: "gh release download with tag [-]--" + pattern: "gh release download" +--- + + +Author `.github/workflows/cmake.yml` — the CI workflow that proves Phase 1 success criterion 5: +"C++ core builds and tests on self-hosted runners for macOS, Linux, Windows, iOS, Android." + +Purpose: This is a near-verbatim copy of `../GeniusSDK/.github/workflows/cmake.yml` (the +consumer-repo template) with four deltas: + 1. Add a GeniusSDK release download step (GCS consumes GeniusSDK prebuilt; GeniusSDK repo + builds itself) + 2. Delete the zkLLVM download step (D-09) + 3. Change working-directory from `${{github.workspace}}/GeniusSDK` to `${{github.workspace}}` + (GCS checkout IS the workspace root) + 4. Add ctest steps copied from `../SuperGenius/.github/workflows/cmake.yml:703-740` (the + GeniusSDK workflow has no ctest steps; SuperGenius does) + +Output: A single workflow file. The first CI run will validate release freshness (esp. the +`GeniusSDKGetNode` symbol in the GeniusSDK develop release — see RESEARCH Assumption A3 and +Pitfall 1). + + + +@$HOME/.claude/get-shit-done/workflows/execute-plan.md +@$HOME/.claude/get-shit-done/templates/summary.md + + + +@.planning/workstreams/app/STATE.md +@.planning/workstreams/app/phases/01-foundation/01-CONTEXT.md +@.planning/workstreams/app/phases/01-foundation/01-RESEARCH.md +@.planning/workstreams/app/phases/01-foundation/01-PATTERNS.md + +@../GeniusSDK/.github/workflows/cmake.yml +@../SuperGenius/.github/workflows/cmake.yml + + + +Tag format: [-]-- + - ABI segment present only for Android (arm64-v8a, armeabi-v7a) and Linux (x86_64, aarch64) + - Examples: OSX-develop-Debug, Linux-x86_64-develop-Release, Android-arm64-v8a-develop-Debug +Asset file name: [-]-.tar.gz + +Runner labels (D-06): + self-hosted: sg-ubuntu-linux (Linux x86_64 + Android), sg-arm-linux (Linux aarch64), + SG-WIN11 (Windows), gv-OSX-Large (OSX + iOS) + fallback: ubuntu-latest, ubuntu-24.04-arm, windows-2022, macos-latest + +Container: ghcr.io/geniusventures/debian-bullseye:latest on Linux/Android + +CI must pass -DTHIRDPARTY_BUILD_DIR explicitly (Pitfall 3): the extracted tarball has no .git, +so get_third_party_dir's remote check would fail without it. + + + + + + + Task 1: Author .github/workflows/cmake.yml (GeniusSDK template + 4 deltas) + .github/workflows/cmake.yml + + - ../GeniusSDK/.github/workflows/cmake.yml (FULL file, 594 lines — the template) + - ../SuperGenius/.github/workflows/cmake.yml lines 700-740 (the ctest steps to copy) + - .planning/workstreams/app/phases/01-foundation/01-PATTERNS.md § ".github/workflows/cmake.yml" + - .planning/workstreams/app/phases/01-foundation/01-RESEARCH.md § "CI/CD (D-06..D-09)" + + + Create .github/workflows/cmake.yml. Structure (sections in order): + + 1. Trigger + permissions block (copy GeniusSDK lines 1-26 verbatim): + - name: Release Build CI + - on: push/PR to develop+main, paths-ignore .github/** and Readme.md, workflow_dispatch + with optional 'tag' input + - permissions: contents: write, packages: read + + 2. resolve-runners job (copy GeniusSDK lines 28-125 verbatim — the pick_runner() bash function + with all four self-hosted labels and fallbacks, using secrets.GNUS_TOKEN_1 and gh api for + repo+org runner merge). NO changes. + + 3. build job with matrix (copy GeniusSDK lines 138-191 verbatim): + - 5 targets × Debug/Release + - ABI includes for Linux (x86_64, aarch64) and Android (arm64-v8a, armeabi-v7a) + - excludes for empty-ABI Linux/Android + - container ghcr.io/geniusventures/debian-bullseye:latest on Linux/Android + - runs-on: from resolve-runners output + + 4. Self-hosted cleanup step (copy GeniusSDK lines 214-267 verbatim). KEEP the zkLLVM cleanup + line even though GCS skips the explicit zkLLVM step — stale dirs from prior runs could + exist on the self-hosted runner. + + 5. Checkout step (modify): + - name: Checkout GCS + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + - with: submodules: "recursive", persist-credentials: true + - NO `path:` override (workspace IS the GCS repo root) + - Comment noting GCS submodules: GNUS-NEO-SWARM, build (cmaketemplate), gendoc-template, + src/app/scaffold + + 6. Download thirdparty release (copy GeniusSDK lines 286-353 verbatim). Sets + THIRDPARTY_BUILD_DIR env var. + + 7. DELETE the zkLLVM download step (GeniusSDK lines 355-409). Per D-09 + RESEARCH item 3: + cmaketemplate auto-downloads zkLLVM headers at configure time; "skip zkLLVM" means no + explicit CI step. Add a comment noting this decision. + + 8. Download SuperGenius release (copy GeniusSDK lines 411-450 verbatim). Sets SUPERGENIUS_DIR. + + 9. NEW STEP — Download GeniusSDK release (insert after SuperGenius step). Shape per + PATTERNS.md § "NEW: GeniusSDK release download": + - Same tag/asset pattern as thirdparty + - mkdir GeniusSDK && cd GeniusSDK + - gh release download --repo GeniusVentures/GeniusSDK -p "" + - tar -xzf + - Set GENIUS_SDK_DIR env var (use $(pwd -W) on Windows, $(pwd) elsewhere) + + 10. Per-platform host setup (copy GeniusSDK lines 452-502 verbatim): + - Linux: update-alternatives for clang + apt install ccache ninja-build libvulkan-dev + libzstd-dev libsecret-1-dev -y + - Windows: choco install ccache -A + - macOS: brew install ninja bash gnu-tar + GNU tar PATH fixup + - Android: NDK r27b download + - Decision: drop the rustup lines from Android setup IF GCS has no Rust — GCS has no + Cargo.toml or .rs files (verify by `find . -name "*.rs" -not -path "./thirdparty/*" | + head -1` returning empty), so drop them. Note the deletion in a comment. + + 11. CMake configure steps (copy GeniusSDK lines 504-527 with these changes): + - working-directory: ${{github.workspace}} (NOT ${{github.workspace}}/GeniusSDK) + - Drop -DZKLLVM_BUILD_DIR (no longer downloaded) + - Drop -DSUPERGENIUS_DIR (GCS uses sibling auto-detect per RESEARCH item 4 — the + PROJECT_SUPER_ROOT walk finds SuperGenius/GeniusSDK as siblings of the workspace) + - Keep -DTHIRDPARTY_BUILD_DIR=${{env.THIRDPARTY_BUILD_DIR}} (mandatory per Pitfall 3) + - Per platform: + OSX: cmake -S build/OSX -B $BUILD_DIRECTORY -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -DTHIRDPARTY_BUILD_DIR=${{env.THIRDPARTY_BUILD_DIR}} + Linux: cmake -S build/Linux -B $BUILD_DIRECTORY -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -DTHIRDPARTY_BUILD_DIR=${{env.THIRDPARTY_BUILD_DIR}} + Windows: cmake -S build/Windows -B $BUILD_DIRECTORY -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -DTHIRDPARTY_BUILD_DIR=${{env.THIRDPARTY_BUILD_DIR}} + Android: cmake -S build/Android -B $BUILD_DIRECTORY -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -DANDROID_ABI=${{matrix.abi}} -DCMAKE_ANDROID_NDK=$ANDROID_NDK_HOME -DTHIRDPARTY_BUILD_DIR=${{env.THIRDPARTY_BUILD_DIR}} + iOS: cmake -S build/iOS -B $BUILD_DIRECTORY -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -DPLATFORM=OS64 -DTHIRDPARTY_BUILD_DIR=${{env.THIRDPARTY_BUILD_DIR}} + (No -DCMAKE_TOOLCHAIN_FILE flag. PINNED 2026-08-15 from build/iOS/CMakeLists.txt + lines 10-19: when CMAKE_TOOLCHAIN_FILE is undefined, the build/iOS CMakeLists + auto-sets it to ${CMAKE_CURRENT_SOURCE_DIR}/../apple.toolchain.cmake — i.e. + build/apple.toolchain.cmake inside the cmaketemplate submodule — along with + PLATFORM=OS64, DEPLOYMENT_TARGET=15, ENABLE_BITCODE=OFF, ENABLE_ARC=OFF, + ENABLE_VISIBILITY=ON. Do NOT add an explicit toolchain flag; doing so would + override the auto-set block and risks drifting from the cmaketemplate default.) + + 12. Build step (copy GeniusSDK lines 529-533 verbatim, working-directory becomes + ${{github.workspace}}/${{env.BUILD_DIRECTORY}}). + + 13. Install step (copy GeniusSDK line 534-536 verbatim). + + 14. ctest steps (copy from SuperGenius lines 703-740 — these do NOT exist in the GeniusSDK + workflow, this is a NET-NEW addition): + - Linux: dbus-run-session + gnome-keyring stub wrapper + ctest (harmless for GCS per + RESEARCH A4; copy verbatim as zero-cost insurance) + - OSX: plain ctest + - Windows: ctest, Release only + - NO ctest on Android/iOS (cross-compiles) + + 15. Release upload steps (copy GeniusSDK lines 538-593 verbatim, with these changes): + - Replace `GeniusSDK` repo references with `GeniusCogntiveSystem` + - The tar --transform path needs adjusting: GCS ships from the workspace root, so the + top-level dirs to package are the built artifacts (libgcs_ffi + libgcs_core + + headers). Planner's discretion on the exact --transform — recommend keeping the same + shape and pointing at ${{env.BUILD_DIRECTORY}}. + - Decision per RESEARCH item 8: INCLUDE the release upload this phase (it smoke-proves + artifact packaging). + + Total file size: ~600 lines, 95% copied. The deltas are: + 1. New GeniusSDK download step + 2. zkLLVM step deleted + 3. working-directory: workspace root (not workspace/GeniusSDK) + 4. ctest steps added (from SuperGenius) + 5. rustup lines dropped from Android setup (no Rust in GCS) + 6. Repo name in release-upload steps + + Style: this is YAML, not C++. The CLAUDE.md C++ style rules don't apply. Match the template's + existing indentation (2 spaces) and quoting style exactly. + + + test -f .github/workflows/cmake.yml && grep -q "resolve-runners" .github/workflows/cmake.yml && grep -q "GeniusVentures/GeniusSDK" .github/workflows/cmake.yml && ! grep -q "zkLLVM release\|Download zkLLVM" .github/workflows/cmake.yml && grep -cE "ctest" .github/workflows/cmake.yml | grep -qE "^[3-9]|^[1-9][0-9]" && python3 -c "import yaml,sys; yaml.safe_load(open('.github/workflows/cmake.yml'))" && echo "yaml-valid" + + + - File exists at .github/workflows/cmake.yml + - YAML parses cleanly + - resolve-runners job present + - GeniusSDK download step present + - No zkLLVM download step (comment noting the deletion is fine; the step itself is gone) + - At least 3 ctest invocations (Linux, OSX, Windows Release) + - No ctest under Android or iOS matrix conditions + - working-directory is ${{github.workspace}} or ${{github.workspace}}/${{env.BUILD_DIRECTORY}} — never ${{github.workspace}}/GeniusSDK + - Trigger includes push to develop+main, PR to develop+main, workflow_dispatch + + Workflow file authored and YAML-valid; ready for push + first CI run. + + + + Task 2: Push to develop and observe first CI run + .github/workflows/cmake.yml + + The complete CI workflow from Task 1, plus all preceding plans (01-01 through 01-05) merged + to develop. The first CI run on push is the proof that: + - Self-hosted runners pick up the job (or fall back to GitHub-hosted) + - All three dependency releases download and extract correctly + - CMake configure resolves all paths with -DTHIRDPARTY_BUILD_DIR + - All 5 platforms build + - ctest passes on Linux, OSX, Windows Release + - Release artifacts upload + + + This task requires the user to push (per project rule: "Do not commit changes without + explicit user permission" applies at push time, and only the user can authorize the first + CI run). + + Steps for the user: + 1. Confirm all Wave 1-3 plans (01-01..01-05) are merged to develop locally. + 2. Confirm the GNUS-NEO-SWARM submodule pointer on develop references a commit that + includes the Plan 01 accessors (push the neoswarm feature branch first if not already + pushed, then bump the submodule pointer). + 3. Push to origin/develop. + 4. Watch the CI run: `gh run watch` or via the GitHub Actions web UI. + 5. If any matrix cell fails: + - "Undefined symbols: GeniusSDKGetNode" on any platform → GeniusSDK develop release + is stale; re-release GeniusSDK and re-run (RESEARCH Pitfall 1). + - "Cannot find thirdparty directory" → -DTHIRDPARTY_BUILD_DIR wasn't threaded through + on that platform's configure step; fix and re-run. + - Runner label not found → check self-hosted runner registration for that label. + 6. Confirm all 12 matrix cells are green (or accept documented cross-compile-only cells + skipping ctest). + + Type "ci-green" with the run URL, or describe which matrix cells failed and the error snippets. + + + + + +- Workflow file passes `python3 -c "import yaml; yaml.safe_load(...)"` syntax check +- First CI run on push to develop: all 12 matrix cells build successfully; ctest green on + Linux, OSX, Windows Release; no ctest on Android/iOS +- Release artifacts uploaded for each cell + + + +- ROADMAP success criterion 5 ("CI/CD pipeline builds and tests on self-hosted runners for + macOS, Linux, Windows, iOS, Android") is green +- All 5 platforms produce installable artifacts +- The GCS repo now has the same CI infrastructure maturity as SuperGenius and GeniusSDK + + + +Create `.planning/workstreams/app/phases/01-foundation/01-06-SUMMARY.md` when done. + diff --git a/.planning/workstreams/app/phases/01-foundation/01-RESEARCH.md b/.planning/workstreams/app/phases/01-foundation/01-RESEARCH.md index 997ecfb..77d34e4 100644 --- a/.planning/workstreams/app/phases/01-foundation/01-RESEARCH.md +++ b/.planning/workstreams/app/phases/01-foundation/01-RESEARCH.md @@ -398,22 +398,25 @@ endif() | A4 | Plain `ctest` suffices for GCS smoke tests on Linux (no gnome-keyring wrapper needed) because tests use the injected-pubsub seam and never init GeniusSDK | CI/CD item 5 | Low — copying the dbus-run-session wrapper verbatim is zero-cost insurance if wrong. | | A5 | Letting cmaketemplate's zkLLVM auto-download run in CI (headers only) satisfies D-09's "skip zkLLVM" intent | CI/CD item 3 | Low — worst case CI downloads a tarball it doesn't compile against; confirm with user if "skip" meant "zero zkLLVM artifacts". | -## Open Questions +## Open Questions (RESOLVED) 1. **D-05 references a neoswarm_ffi callback mechanism that doesn't exist.** - What we know: neoswarm_ffi is the stock Flutter plugin template; zero `Dart_PostCObject`/`NativePort` usage anywhere in the workspace's C++/Dart code. - What's unclear: Whether the user has seen this pattern in some other repo (GeniusWallet? a branch not fetched?) that should be the reference instead of the standard Dart API_DL idiom. - Recommendation: Plan a small spike task ("stand up minimal NativePort echo through gcs_ffi locally") before the full FFI task; implement the standard mechanism; flag to user at plan review. + - **RESOLVED (2026-08-15):** Plan 01-05 spike implements the standard Dart API_DL NativePort pattern (`Dart_InitializeApiDL` + `Dart_PostCObject` + `ReceivePort`). No alternate in-repo reference surfaced; the standard mechanism is the design. 2. **CRDT round-trip needs GcsGlobalDb accessors (Put/Get/AddBroadcastTopic) that don't exist yet.** - What we know: `GcsGlobalDb` public API is lifecycle-only (Initialize/Shutdown/IsRunning); `m_db` is private. - What's unclear: Whether Phase 1 may extend `GcsGlobalDb` in the GNUS-NEO-SWARM submodule (separate branch/PR there) or whether CORE-05's smoke proof should use raw `GossipPubSub::Publish` + GlobalDB accessed some other way. - Recommendation: Planner sequences a submodule task adding minimal read-only accessors (or pass-throughs) to `GcsGlobalDb`, committed on a neoswarm branch, before the GCS smoke test task. Confirm scope with user since STATE.md lists "Phase 1 depends on GlobalDB CRDT integration from GNUS-NEO-SWARM Phase 3" as a concern. + - **RESOLVED (2026-08-15):** Plan 01-01 adds the four pass-through accessors (`AddBroadcastTopic`, `AddListenTopic`, `Put`, `Get`) to `GcsGlobalDb` on a GNUS-NEO-SWARM feature branch (`feature/gcs-globaldb-passthrough`, stacked on `feature/app-restructure`). 3. **zkLLVM in CI: header-only consumption vs. full skip.** - What we know: `CommonCompilerOptions.cmake` auto-downloads zkLLVM when absent; `neoswarm_storage` adds its include dir conditionally; D-09 says skip zkLLVM. - What's unclear: Whether "skip" means "no explicit CI step" (auto-download still runs, headers only) or "zero zkLLVM presence" (requires a CMake skip flag that doesn't exist yet). - Recommendation: Default to no-explicit-step (option 1); if the user wants zero-presence, planner adds a `GCS_SKIP_ZKLLVM`-style guard to CommonCompilerOptions — small but touches the cmaketemplate submodule. + - **RESOLVED (2026-08-15):** Plan 01-06 keeps cmaketemplate's auto-download (headers-only consumption at configure time) and adds no explicit zkLLVM CI step. D-09 is interpreted as "no explicit CI step" — zero-presence is not required. ## Environment Availability diff --git a/.planning/workstreams/app/phases/01-foundation/01-VALIDATION.md b/.planning/workstreams/app/phases/01-foundation/01-VALIDATION.md index 05285dd..6953b83 100644 --- a/.planning/workstreams/app/phases/01-foundation/01-VALIDATION.md +++ b/.planning/workstreams/app/phases/01-foundation/01-VALIDATION.md @@ -1,8 +1,8 @@ --- phase: 1 slug: foundation -status: draft -nyquist_compliant: false +status: ready +nyquist_compliant: true wave_0_complete: false created: 2026-08-15 --- @@ -17,20 +17,21 @@ created: 2026-08-15 | Property | Value | |----------|-------| -| **Framework** | {pytest 7.x / jest 29.x / vitest / go test / other} | -| **Config file** | {path or "none — Wave 0 installs"} | -| **Quick run command** | `{quick command}` | -| **Full suite command** | `{full command}` | -| **Estimated runtime** | ~{N} seconds | +| **Framework** | Google Test (thirdparty-provided; `find_package(GTest CONFIG REQUIRED)` in root CMakeLists under `BUILD_TESTING`) + Dart `flutter_test` for the Dart-side smoke | +| **Config file** | `test/CMakeLists.txt` (GCS root) — created in Plan 01-04 Task 1; `build/CommonBuildParameters.cmake:146-151` already has the `add_subdirectory(${PROJECT_ROOT}/test ...)` hook gated on `BUILD_TESTING` | +| **Quick run command** | `cmake --build build/OSX/Debug -j && ctest --test-dir build/OSX/Debug -R gcs --output-on-failure` | +| **Full suite command** | `ctest --test-dir build/OSX/Debug --output-on-failure` (includes neoswarm tests, which also build in the parent build) | +| **Dart-side smoke** | `cd src/app && flutter test test/gcs_native_port_smoke_test.dart` (Plan 01-05 Task 4 — human-verify checkpoint) | +| **Estimated runtime** | ~90 seconds for the ctest -R gcs quick run; full parent-build suite longer | --- ## Sampling Rate -- **After every task commit:** Run `{quick run command}` -- **After every plan wave:** Run `{full suite command}` -- **Before `/gsd:verify-work`:** Full suite must be green -- **Max feedback latency:** {N} seconds +- **After every task commit:** Run `cmake --build build/OSX/Debug -j && ctest --test-dir build/OSX/Debug -R gcs --output-on-failure` +- **After every plan wave:** Run `ctest --test-dir build/OSX/Debug --output-on-failure` (full parent-build suite) +- **Before `/gsd:verify-work`:** Full suite must be green on OSX; CI green on all 5 platforms via Plan 01-06 +- **Max feedback latency:** ~90 seconds (OSX Debug incremental build + `ctest -R gcs`) --- @@ -38,7 +39,24 @@ created: 2026-08-15 | Task ID | Plan | Wave | Requirement | Threat Ref | Secure Behavior | Test Type | Automated Command | File Exists | Status | |---------|------|------|-------------|------------|-----------------|-----------|-------------------|-------------|--------| -| {N}-01-01 | 01 | 1 | REQ-{XX} | T-{N}-01 / — | {expected secure behavior or "N/A"} | unit | `{command}` | ✅ / ❌ W0 | ⬜ pending | +| 01-01-01 | 01 | 1 | CORE-05 | — | Public pass-through declarations on GcsGlobalDb (no behavior change) | static / grep | `grep -c "AddBroadcastTopic\|AddListenTopic\|::Put(\|::Get(" GNUS-NEO-SWARM/src/storage/gcs_global_db.hpp \| grep -q "^[4-9]\|^[1-9][0-9]"` | ❌ W0 | ⬜ pending | +| 01-01-02 | 01 | 1 | CORE-05 | T-01-04 (double-init / not-running misuse) | All four pass-throughs return `outcome::failure(Error::GcsDbError)` when `!m_running.load()` | static / grep + git diff | `cd GNUS-NEO-SWARM && git diff --stat HEAD && grep -c "GcsGlobalDb::AddBroadcastTopic\|GcsGlobalDb::AddListenTopic\|GcsGlobalDb::Put\|GcsGlobalDb::Get" src/storage/gcs_global_db.cpp \| grep -q "^4$"` | ❌ W0 | ⬜ pending | +| 01-01-03 | 01 | 1 | CORE-05 | — | Local commit on `feature/gcs-globaldb-passthrough` (stacked on `feature/app-restructure`); GCS submodule pointer NOT staged | git | `git -C GNUS-NEO-SWARM log -1 --oneline \| grep -q "pass-throughs to GcsGlobalDb"` | ❌ W0 | ⬜ pending | +| 01-02-01 | 02 | 2 | CORE-05 | — | `gcs_core PUBLIC neoswarm_storage` link is hard-required (configure-time FATAL_ERROR if missing) | static / grep | `grep -A1 "if(TARGET neoswarm_storage" src/CMakeLists.txt \| grep -q "target_link_libraries(gcs_core PUBLIC neoswarm_storage)"` | ❌ W0 | ⬜ pending | +| 01-02-02 | 02 | 2 | CORE-05 | — | `gcs::CoreSession` interface declared (header-only) | static / grep | `grep -c "class CoreSession\|Initialize\|Shutdown\|IsRunning\|AddBroadcastTopic\|AddListenTopic\|Put\|Get" src/lib/gcs_core.hpp \| grep -q "^[8-9]\|^[1-9][0-9]"` | ❌ W0 | ⬜ pending | +| 01-02-03 | 02 | 2 | CORE-05 | — | `gcs_core` static lib builds cleanly on OSX Debug with Plan 01-01's submodule branch checked out | build | `cmake --build build/OSX/Debug -j 2>&1 \| tail -20 \| grep -E "error\|FAILED" ; test ${PIPESTATUS[0]} -eq 0` | ❌ W0 | ⬜ pending | +| 01-03-01 | 03 | 3 | CORE-05 | — | `gcs_ffi` SHARED CMake target wired; `add_subdirectory(ffi)` lands | static / grep | `grep -q "add_library(gcs_ffi SHARED" src/ffi/CMakeLists.txt && grep -q "add_subdirectory(ffi)" src/CMakeLists.txt` | ❌ W0 | ⬜ pending | +| 01-03-02 | 03 | 3 | CORE-05 | T-01-05 (FFI export surface) | Single opaque-handle C API declared; `_WIN32` dllexport macro block is the ONLY OS preprocessor guard | static / grep | `grep -cE "GCS_FFI_API.*gcs_(init\|shutdown\|join_topic\|publish\|on_message\|string_free)" src/ffi/gcs_core.h \| grep -q "^6$"` | ❌ W0 | ⬜ pending | +| 01-03-03 | 03 | 3 | CORE-05 | T-01-02 (heap mismatch) / T-01-04 (double-shutdown) | All six `gcs_*` symbols exported from `libgcs_ffi.{dylib,so,dll}`; every function takes the global mutex; `gcs_on_message` stubbed to `GCS_ERROR_NOT_IMPLEMENTED` | build + nm | `cmake --build build/OSX/Debug -j 2>&1 \| tee /tmp/gcs_ffi_build.log \| tail -5 ; test -f build/OSX/Debug/gcs_src/ffi/libgcs_ffi.dylib && nm -gU build/OSX/Debug/gcs_src/ffi/libgcs_ffi.dylib \| grep -cE "_gcs_(init\|shutdown\|join_topic\|publish\|on_message\|string_free)" \| grep -q "^6$"` | ❌ W0 | ⬜ pending | +| 01-04-01 | 04 | 3 | CORE-05 | — | Test scaffolding (`gcs_test` macro + WaitForCondition template) present | static / file exists | `test -f test/CMakeLists.txt && test -f test/test_wait_condition.hpp && grep -q "gcs_test(test_gcs_core_smoke" test/CMakeLists.txt && grep -q "WaitForCondition" test/test_wait_condition.hpp` | ❌ W0 | ⬜ pending | +| 01-04-02 | 04 | 3 | CORE-05 | T-01-06 (test flakiness via sleep) | CORE-05 substrate smoke (lifecycle + topic + Put/Get round-trip over port-0 GossipPubSub); zero `sleep_for`/`sleep_until` | unit (gtest) | `grep -c "sleep_for\|sleep_until" test/test_gcs_core_smoke.cpp \| grep -q "^0$" && cmake --build build/OSX/Debug -j 2>&1 \| tail -10 \| grep -E "error\|FAILED" ; ctest --test-dir build/OSX/Debug -R test_gcs_core_smoke --output-on-failure 2>&1 \| tail -15 \| grep -E "Passed\|Failed"` | ❌ W0 | ⬜ pending | +| 01-04-03 | 04 | 3 | CORE-05 | T-01-01 (FFI boundary null/malformed input) / T-01-03 (use-after-free) | FFI surface smoke; `gcs_init` returns `nullptr` gracefully (no crash, no exception) when GeniusSDK node unavailable; argument validation + version + shutdown-safe-on-null tests do not require a live node; **no `gcs_on_message` tests in this plan** | unit (gtest) | `grep -c "sleep_for\|sleep_until" test/test_gcs_ffi.cpp \| grep -q "^0$" && cmake --build build/OSX/Debug -j 2>&1 \| tail -5 \| grep -E "error\|FAILED" ; ctest --test-dir build/OSX/Debug -R test_gcs_ffi --output-on-failure 2>&1 \| tail -15 \| grep -E "Passed\|Failed\|tests passed"` | ❌ W0 | ⬜ pending | +| 01-05-01 | 05 | 4 | CORE-05 | — | Dart API_DL headers vendored; gcs_ffi sources updated | build | `test -f src/ffi/dart_native_api.h && test -f src/ffi/dart_api_dl.c && grep -q "dart_api_dl.c" src/ffi/CMakeLists.txt && cmake --build build/OSX/Debug -j 2>&1 \| tail -5 \| grep -E "error\|FAILED" ; echo "build-ok"` | ❌ W0 | ⬜ pending | +| 01-05-02 | 05 | 4 | CORE-05 | T-01-03 (use-after-free across FFI) | `gcs_on_message` registers port; `gcs_publish` echoes payload via `Dart_PostCObject`; `gcs_shutdown` clears port before teardown | build + ctest | `cmake --build build/OSX/Debug -j 2>&1 \| tail -5 \| grep -E "error\|FAILED" ; ctest --test-dir build/OSX/Debug -R gcs --output-on-failure 2>&1 \| tail -10 \| grep -E "tests passed\|Passed\|Failed"` | ❌ W0 | ⬜ pending | +| 01-05-03 | 05 | 4 | CORE-05 | — | ffigen bindings generated against the real `src/ffi/gcs_core.h` | codegen + grep | `test -f src/app/ffigen.yaml && grep -q "gcs_core.h" src/app/ffigen.yaml && test -f src/app/lib/gcs_bindings_generated.dart && grep -c "gcs_init\|gcs_shutdown\|gcs_on_message" src/app/lib/gcs_bindings_generated.dart \| grep -q "^[3-9]\|^[1-9][0-9]"` | ❌ W0 | ⬜ pending | +| 01-05-04 | 05 | 4 | CORE-05 | — | Dart smoke: `gcs_init` → `gcs_on_message` → `gcs_publish` → `ReceivePort` round-trip (skip-on-null-init only) | manual / flutter test | `cd src/app && flutter test test/gcs_native_port_smoke_test.dart` | ❌ W0 | ⬜ pending (human-verify) | +| 01-06-01 | 06 | 5 | CORE-05 | — | CI workflow YAML valid; resolve-runners present; GeniusSDK download present; zkLLVM step deleted; ≥3 ctest invocations | static / yaml + grep | `test -f .github/workflows/cmake.yml && grep -q "resolve-runners" .github/workflows/cmake.yml && grep -q "GeniusVentures/GeniusSDK" .github/workflows/cmake.yml && ! grep -q "zkLLVM release\|Download zkLLVM" .github/workflows/cmake.yml && grep -cE "ctest" .github/workflows/cmake.yml \| grep -qE "^[3-9]\|^[1-9][0-9]" && python3 -c "import yaml,sys; yaml.safe_load(open('.github/workflows/cmake.yml'))" && echo "yaml-valid"` | ❌ W0 | ⬜ pending | +| 01-06-02 | 06 | 5 | CORE-05 | — | First CI run on push to develop is green across the matrix (OSX/Linux/Windows ctest; Android/iOS build-only) | manual / gh run watch | `gh run watch` (user-driven; Plan 01-06 Task 2 is a `checkpoint:human-action`) | ❌ W0 | ⬜ pending (human-action) | *Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky* @@ -46,11 +64,14 @@ created: 2026-08-15 ## Wave 0 Requirements -- [ ] `{tests/test_file.py}` — stubs for REQ-{XX} -- [ ] `{tests/conftest.py}` — shared fixtures -- [ ] `{framework install}` — if no framework detected +- [ ] `test/CMakeLists.txt` (GCS root) — registers smoke tests; gate on GTest found (Plan 01-04 Task 1) +- [ ] `test/test_wait_condition.hpp` — WaitForCondition template copied verbatim from neoswarm analog (Plan 01-04 Task 1) +- [ ] `test/test_gcs_core_smoke.cpp` — covers CORE-05 substrate (lifecycle + topic + Put/Get round-trip) (Plan 01-04 Task 2) +- [ ] `test/test_gcs_ffi.cpp` — covers FFI init/echo/shutdown, option C semantics (Plan 01-04 Task 3) +- [ ] `src/ffi/CMakeLists.txt` + `gcs_ffi` SHARED target (Plan 01-03 Task 1) +- [ ] `.github/workflows/cmake.yml` — CI workflow (Plan 01-06 Task 1) -*If none: "Existing infrastructure covers all phase requirements."* +`wave_0_complete: false` until all six land. --- @@ -58,19 +79,20 @@ created: 2026-08-15 | Behavior | Requirement | Why Manual | Test Instructions | |----------|-------------|------------|-------------------| -| {behavior} | REQ-{XX} | {reason} | {steps} | +| Dart NativePort round-trip smoke (`gcs_init` → `gcs_on_message` → `gcs_publish` → `ReceivePort` receives echo) | CORE-05 success criterion 3 | Requires a Flutter runtime + a loaded dylib; cannot be driven from ctest. `skip:` only for the live-node init-success path when GeniusSDK is unavailable in the test env. | `cd src/app && flutter test test/gcs_native_port_smoke_test.dart` — expect "+1 passed" or skip-with-note; Plan 01-05 Task 4 human-verify checkpoint | +| First CI run green across the full matrix on push to develop | CORE-05 success criterion 5 | Only the user can authorize the push; requires watching GitHub Actions on self-hosted runners | `gh run watch` or GitHub Actions web UI; Plan 01-06 Task 2 human-action checkpoint | -*If none: "All phase behaviors have automated verification."* +All other phase behaviors have automated verification. --- ## Validation Sign-Off -- [ ] All tasks have `` verify or Wave 0 dependencies -- [ ] Sampling continuity: no 3 consecutive tasks without automated verify -- [ ] Wave 0 covers all MISSING references -- [ ] No watch-mode flags -- [ ] Feedback latency < {N}s -- [ ] `nyquist_compliant: true` set in frontmatter +- [x] All tasks have `` verify or Wave 0 dependencies +- [x] Sampling continuity: no 3 consecutive tasks without automated verify +- [ ] Wave 0 covers all MISSING references (Wave 0 deliverables listed above; complete on Plan 01-03 + 01-04 + 01-06 landing) +- [x] No watch-mode flags +- [x] Feedback latency < 90s +- [x] `nyquist_compliant: true` set in frontmatter -**Approval:** {pending / approved YYYY-MM-DD} +**Approval:** pending — wave 0 artifacts land across Plans 01-03, 01-04, 01-06; sign-off after first green `ctest -R gcs` on OSX Debug. From b66cff4af5a28fe5c30bb7643c441a5c76f4d4e8 Mon Sep 17 00:00:00 2001 From: Super Genius Date: Sat, 15 Aug 2026 19:40:08 -0700 Subject: [PATCH 15/49] docs(01): create phase plan --- .planning/workstreams/app/ROADMAP.md | 36 + .planning/workstreams/app/STATE.md | 12 +- .../app/phases/01-foundation/01-03-PLAN.md | 6 +- .../app/phases/01-foundation/01-06-PLAN.md | 6 +- .../app/phases/01-foundation/01-CONTEXT.md | 2 +- .../app/phases/01-foundation/01-PATTERNS.md | 1099 +++++++++++++++++ 6 files changed, 1149 insertions(+), 12 deletions(-) create mode 100644 .planning/workstreams/app/phases/01-foundation/01-PATTERNS.md diff --git a/.planning/workstreams/app/ROADMAP.md b/.planning/workstreams/app/ROADMAP.md index 9460bf0..6914046 100644 --- a/.planning/workstreams/app/ROADMAP.md +++ b/.planning/workstreams/app/ROADMAP.md @@ -7,6 +7,7 @@ Build a working multi-party chat application where users create spaces and rooms ## Phases **Phase Numbering:** + - Integer phases (1, 2, 3): Planned milestone work - Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED) @@ -23,96 +24,131 @@ Decimal phases appear between their surrounding integers in numeric order. ## Phase Details ### Phase 1: Foundation + **Goal**: The C++ chat core compiles, links against GlobalDB, and exposes a working FFI surface that Flutter can call. **Depends on**: Nothing (first phase) **Requirements**: CORE-05 **Success Criteria** (what must be TRUE): + 1. C++ core library builds on macOS, Linux, Windows, iOS, and Android without errors 2. GlobalDB CRDT instance can be initialized and a test op can be published/subscribed locally 3. Flutter app can call into C++ via FFI and receive a response 4. GossipSub topics can be created and joined from the C++ core 5. CI/CD pipeline builds and tests on self-hosted runners for macOS, Linux, Windows, iOS, Android + **Plans:** 6 plans Plans: +**Wave 1** + - [ ] 01-01-PLAN.md — Wave 1 — GcsGlobalDb pass-through accessors (GNUS-NEO-SWARM submodule branch, stacked on feature/app-restructure) + +**Wave 2** *(blocked on Wave 1 completion)* + - [ ] 01-02-PLAN.md — Wave 2 (depends: 01-01) — Wire gcs_core to neoswarm_storage; create gcs::CoreSession class + +**Wave 3** *(blocked on Wave 2 completion)* + - [ ] 01-03-PLAN.md — Wave 3 (depends: 01-01, 01-02) — gcs_ffi SHARED library + single opaque-handle C API - [ ] 01-04-PLAN.md — Wave 3 (depends: 01-01, 01-02) — Test scaffolding + CORE-05 smoke tests (lifecycle, CRDT round-trip, FFI option-C init) + +**Wave 4** *(blocked on Wave 3 completion)* + - [ ] 01-05-PLAN.md — Wave 4 (depends: 01-03, 01-04) — Dart NativePort callback spike + ffigen bindings + Dart smoke test (owns all on_message tests) + +**Wave 5** *(blocked on Wave 4 completion)* + - [ ] 01-06-PLAN.md — Wave 5 (depends: 01-03, 01-04) — CI workflow (.github/workflows/cmake.yml) for all 5 platforms ### Phase 2: Spaces & Rooms + **Goal**: Users can create spaces and rooms with configurable inheritance, and the entity hierarchy is persisted via CRDT. **Depends on**: Phase 1 **Requirements**: CORE-01, CORE-02, CORE-03 **Success Criteria** (what must be TRUE): + 1. User can create a public or private space and see it in their local space list 2. User can create a room within a space or as a standalone room 3. User can toggle `autoJoinRooms` on a space and observe the join behavior change 4. Space/room metadata survives app restart (CRDT persistence) + **Plans**: TBD **UI hint**: yes ### Phase 3: Messaging + **Goal**: Users can send and receive text messages in real-time, with all room participants converging on the same message history via CRDT. **Depends on**: Phase 2 **Requirements**: CORE-04 **Success Criteria** (what must be TRUE): + 1. User can send a text message to a room and see it appear locally 2. A second user in the same room receives the message without manual refresh 3. Message history is identical across all participants after sync 4. Messages display in chronological order with sender identification + **Plans**: TBD **UI hint**: yes ### Phase 4: Membership & Invites + **Goal**: Users can invite others via capability tokens, and the five-tier role model (Super Admin → Guest) governs what actions each participant can take. **Depends on**: Phase 3 **Requirements**: MEMB-01, MEMB-02, MEMB-03, MEMB-04, MEMB-05 **Success Criteria** (what must be TRUE): + 1. User can generate an invite link (`gcs://invite/...`) for a space or room 2. Recipient can use the invite to join and appears in the member list 3. Each participant has a visible role (Super Admin, Admin, Moderator, Member, Guest) 4. Super Admin cannot be demoted by other admins 5. When 2+ admins exist, destructive actions require Super Admin approval + **Plans**: TBD **UI hint**: yes ### Phase 5: Moderation + **Goal**: Moderators and admins can manage room content and membership through tombstone-based CRDT ops that preserve history while hiding removed content. **Depends on**: Phase 4 **Requirements**: MODR-01, MODR-02, MODR-03 **Success Criteria** (what must be TRUE): + 1. Moderator can delete a message and it disappears from all participants' views 2. Deleted message content is preserved in the CRDT (tombstone, not removal) 3. Moderator can kick a user from a room; the user loses access immediately 4. Moderator can ban a user; the ban persists across rejoin attempts + **Plans**: TBD **UI hint**: yes ### Phase 6: GCS Bot + **Goal**: The GCS system joins rooms as a participant and responds to messages according to the room's `autoAnswer` policy, routing requests through ELM specialist selection. **Depends on**: Phase 3 **Requirements**: GCSB-01, GCSB-02, GCSB-03, GCSB-04 **Success Criteria** (what must be TRUE): + 1. GCS bot appears in the room participant list 2. When `autoAnswer: GCS`, the bot responds to every message automatically 3. When `autoAnswer: None`, the bot responds only when `@gcs` is mentioned 4. Bot responses are routed through the correct ELM specialist for the query type 5. Bot messages appear in the same CRDT message stream as human messages + **Plans**: TBD **UI hint**: yes ### Phase 7: Discovery + **Goal**: Users can browse public spaces in a global lobby, while private spaces remain invisible to the network. **Depends on**: Phase 2 **Requirements**: DISC-01, DISC-02, DISC-03 **Success Criteria** (what must be TRUE): + 1. Creating a public space publishes its existence to the lobby topic 2. Private spaces never appear in the lobby 3. User can open the lobby and see a list of public spaces 4. User can join a public space directly from the lobby + **Plans**: TBD **UI hint**: yes diff --git a/.planning/workstreams/app/STATE.md b/.planning/workstreams/app/STATE.md index 1b8347b..f381f84 100644 --- a/.planning/workstreams/app/STATE.md +++ b/.planning/workstreams/app/STATE.md @@ -2,14 +2,14 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: GCS Chat -status: planning +status: executing stopped_at: Phase 1 context gathered -last_updated: "2026-08-16T01:19:53.328Z" -last_activity: 2026-08-15 — Roadmap created for v1.0 GCS Chat +last_updated: "2026-08-16T02:39:53.797Z" +last_activity: 2026-08-16 -- Phase 01 planning complete progress: total_phases: 7 completed_phases: 0 - total_plans: 0 + total_plans: 6 completed_plans: 0 percent: 0 --- @@ -27,8 +27,8 @@ See: .planning/workstreams/app/PROJECT.md (updated 2026-08-15) Phase: 1 of 7 (Foundation) Plan: 0 of TBD in current phase -Status: Ready to plan -Last activity: 2026-08-15 — Roadmap created for v1.0 GCS Chat +Status: Ready to execute +Last activity: 2026-08-16 -- Phase 01 planning complete Progress: [░░░░░░░░░░] 0% diff --git a/.planning/workstreams/app/phases/01-foundation/01-03-PLAN.md b/.planning/workstreams/app/phases/01-foundation/01-03-PLAN.md index 4b1bdec..ba3a78a 100644 --- a/.planning/workstreams/app/phases/01-foundation/01-03-PLAN.md +++ b/.planning/workstreams/app/phases/01-foundation/01-03-PLAN.md @@ -13,9 +13,11 @@ autonomous: true requirements: [CORE-05] must_haves: truths: - - "gcs_ffi SHARED library target builds and links against gcs_core" - - "src/ffi/gcs_core.h declares a single opaque-handle C session API: gcs_init, gcs_shutdown, gcs_join_topic, gcs_publish, gcs_on_message (declaration only — implementation stubbed pending Plan 05 spike), gcs_string_free" + - "D-02: gcs_ffi SHARED library target builds and links against gcs_core" + - "D-01: src/ffi/gcs_core.h declares a single opaque-handle C session API: gcs_init, gcs_shutdown, gcs_join_topic, gcs_publish, gcs_on_message (declaration only — implementation stubbed pending Plan 05 spike), gcs_string_free" - "All FFI functions are noexcept, null-tolerant, and guarded by a global mutex (per genius_elm_chat_completions idiom)" + - "D-04: all chat state lives in C++ (CoreSession); the C API is a thin thunk — Dart renders and forwards input only" + - "D-03: neoswarm_ffi is not touched by this plan — it stays working; its retirement is re-evaluated at Phase 6" - "The GCS_FFI_API export macro is the ONLY permitted OS preprocessor guard in the new files" - "Build produces libgcs_ffi.{dylib,so,dll} in the build tree" artifacts: diff --git a/.planning/workstreams/app/phases/01-foundation/01-06-PLAN.md b/.planning/workstreams/app/phases/01-foundation/01-06-PLAN.md index 29d5141..4ec3650 100644 --- a/.planning/workstreams/app/phases/01-foundation/01-06-PLAN.md +++ b/.planning/workstreams/app/phases/01-foundation/01-06-PLAN.md @@ -19,10 +19,10 @@ user_setup: location: "GitHub repo Settings -> Actions -> Runners" must_haves: truths: - - ".github/workflows/cmake.yml exists at GCS repo root and triggers on push to develop+main, PRs to develop+main, and workflow_dispatch" - - "Workflow downloads three prebuilt dependency releases (thirdparty, SuperGenius, GeniusSDK) — never builds them from source" + - "D-06: .github/workflows/cmake.yml exists at GCS repo root, copies the SuperGenius resolve-runners + matrix pattern, and triggers on push to develop+main, PRs to develop+main, and workflow_dispatch" + - "D-07: Workflow downloads three prebuilt dependency releases (thirdparty, SuperGenius, GeniusSDK) — never builds them from source" - "Matrix covers all 5 platforms (Android arm64-v8a+armeabi-v7a, iOS, OSX, Linux x86_64+aarch64, Windows) × Debug/Release" - - "ctest runs on Linux, OSX, Windows Release; NOT on Android/iOS (cross-compiles)" + - "D-08: CI builds and ctests the C++ core only (no Flutter app build this phase); ctest runs on Linux, OSX, Windows Release; NOT on Android/iOS (cross-compiles)" - "zkLLVM has no explicit CI step (D-09) — cmaketemplate's auto-download handles the headers-only consumption" artifacts: - path: ".github/workflows/cmake.yml" diff --git a/.planning/workstreams/app/phases/01-foundation/01-CONTEXT.md b/.planning/workstreams/app/phases/01-foundation/01-CONTEXT.md index 0d7ab28..cd3f374 100644 --- a/.planning/workstreams/app/phases/01-foundation/01-CONTEXT.md +++ b/.planning/workstreams/app/phases/01-foundation/01-CONTEXT.md @@ -18,7 +18,7 @@ The C++ chat core compiles, links against GlobalDB (via GNUS-NEO-SWARM's `neoswa ### FFI Boundary / Library Layout - **D-02:** Separate `gcs_ffi` **shared library** target (not folded into `gcs_core` static lib). `gcs_ffi` links `gcs_core` (and transitively `neoswarm_storage` → `sgns::crdt_globaldb` / `sgns::GeniusSDK_shared`). Flutter loads the shared lib (dylib/so/dll) directly. Mirrors the `neoswarm_ffi` plugin pattern but as a plain shared library the app links. -- **D-03:** `neoswarm_ffi` (GNUS-NEO-SWARM) may no longer be needed by the app once `gcs_ffi` exists — its SLM-facing functions get absorbed behind the GCS session API over time. Do NOT remove it in this phase; leave it working. Re-evaluate at Phase 6 (GCS Bot) when the bot path is wired through gcs_core. +- **D-03:** [informational] `neoswarm_ffi` (GNUS-NEO-SWARM) may no longer be needed by the app once `gcs_ffi` exists — its SLM-facing functions get absorbed behind the GCS session API over time. Do NOT remove it in this phase; leave it working. Re-evaluate at Phase 6 (GCS Bot) when the bot path is wired through gcs_core. ### Async / Event Model Across FFI - **D-04:** C++ owns essentially all state and logic. Dart is a thin wrapper: input (send text, select channel, change config) and output (render what C++ gives it). diff --git a/.planning/workstreams/app/phases/01-foundation/01-PATTERNS.md b/.planning/workstreams/app/phases/01-foundation/01-PATTERNS.md new file mode 100644 index 0000000..66f778a --- /dev/null +++ b/.planning/workstreams/app/phases/01-foundation/01-PATTERNS.md @@ -0,0 +1,1099 @@ +# Phase 1: Foundation - Pattern Map + +**Mapped:** 2026-08-15 +**Files analyzed:** 11 (9 new + 2 modified in GCS root; 1 submodule addition pending user approval) +**Analogs found:** 10 / 11 + +## File Classification + +| New/Modified File | Role | Data Flow | Closest Analog | Match Quality | +|-------------------|------|-----------|----------------|---------------| +| `src/CMakeLists.txt` (modify) | config (CMake target) | build-graph | `GNUS-NEO-SWARM/src/storage/CMakeLists.txt` | exact (hard-required link idiom) | +| `src/lib/gcs_core.cpp` (modify) | service (core session owner) | request-response (sync wrapper around async CRDT) | `GNUS-NEO-SWARM/src/storage/gcs_global_db.{hpp,cpp}` | exact (component lifecycle) | +| `src/ffi/CMakeLists.txt` (new) | config (CMake SHARED lib) | build-graph | `GNUS-NEO-SWARM/neoswarm_ffi/src/CMakeLists.txt` + `src/storage/CMakeLists.txt` | exact (SHARED target) + role-match (export define) | +| `src/ffi/gcs_core.h` (new) | FFI C header (opaque handle) | request-response | `GNUS-NEO-SWARM/src/genius_elm_chat_completions.h` | exact | +| `src/ffi/gcs_core_ffi.cpp` (new) | FFI thunk | request-response | `GNUS-NEO-SWARM/src/genius_elm_chat_completions.cpp` | exact | +| `test/CMakeLists.txt` (new) | config (test registration) | build-graph | `GNUS-NEO-SWARM/test/CMakeLists.txt` | exact | +| `test/test_wait_condition.hpp` (new) | test utility | request-response | `GNUS-NEO-SWARM/test/storage/test_gcs_global_db.cpp:72-90` | exact (copy verbatim) | +| `test/test_gcs_core_smoke.cpp` (new) | test | event-driven (CRDT pub/sub round-trip) | `GNUS-NEO-SWARM/test/storage/test_gcs_global_db.cpp` | exact | +| `test/test_gcs_ffi.cpp` (new) | test | request-response (FFI echo) | `GNUS-NEO-SWARM/test/ffi/test_genius_elm_ffi.cpp` | exact | +| `.github/workflows/cmake.yml` (new) | config (CI) | batch (matrix build/test/release) | `../GeniusSDK/.github/workflows/cmake.yml` | exact (consumer-repo variant) | +| `GNUS-NEO-SWARM/src/storage/gcs_global_db.{hpp,cpp}` (submodule — add Put/Get/AddBroadcastTopic accessors) | service (CRDT pass-through) | CRUD | SuperGenius `src/crdt/globaldb/globaldb.hpp` (underlying API surface) | role-match | + +## Pattern Assignments + +### `src/CMakeLists.txt` (modify — extend existing target) + +**Analog:** `GNUS-NEO-SWARM/src/storage/CMakeLists.txt` + +**Current state** (src/CMakeLists.txt lines 1-16 — what we extend): +```cmake +add_library(gcs_core + lib/gcs_core.cpp +) + +target_include_directories(gcs_core PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} +) + +target_link_libraries(gcs_core PUBLIC + spdlog::spdlog + fmt::fmt +) +``` + +**Hard-required linkage idiom to insert** (lines 14-18 of analog): +```cmake +# neoswarm_storage — hard required (project Conditional Compilation rule: +# missing required libraries fail at configure time, never degrade to a stub). +if(TARGET neoswarm_storage) + target_link_libraries(gcs_core PUBLIC neoswarm_storage) +else() + message(FATAL_ERROR "neoswarm_storage target not found — GNUS-NEO-SWARM must be configured before gcs_core") +endif() +``` + +**Pattern notes:** +- `neoswarm_storage` brings `sgns::crdt_globaldb` + `sgns::GeniusSDK_shared` transitively as `PUBLIC` links (verified: analog lines 14-47), so `gcs_core` needs no additional include dirs. +- Add `add_subdirectory(ffi)` after the `gcs_core` block to wire the new FFI target. + +--- + +### `src/ffi/CMakeLists.txt` (new — SHARED lib target) + +**Analog A (SHARED idiom):** `GNUS-NEO-SWARM/neoswarm_ffi/src/CMakeLists.txt` (lines 8-22): +```cmake +add_library(flutter_slm_bridge SHARED + "flutter_slm_bridge.c" +) + +set_target_properties(flutter_slm_bridge PROPERTIES + PUBLIC_HEADER flutter_slm_bridge.h + OUTPUT_NAME "flutter_slm_bridge" +) + +target_compile_definitions(flutter_slm_bridge PUBLIC DART_SHARED_LIB) + +if (ANDROID) + # Support Android 15 16k page size + target_link_options(flutter_slm_bridge PRIVATE "-Wl,-z,max-page-size=16384") +endif() +``` + +**Analog B (export-define + linkage):** pattern synthesized from `src/storage/CMakeLists.txt` + `genius_elm_chat_completions.h`: +```cmake +add_library(gcs_ffi SHARED + gcs_core_ffi.cpp +) + +set_target_properties(gcs_ffi PROPERTIES + PUBLIC_HEADER gcs_core.h + OUTPUT_NAME "gcs_ffi" +) + +target_link_libraries(gcs_ffi PRIVATE gcs_core) +target_compile_definitions(gcs_ffi PRIVATE GCS_FFI_EXPORTS) # dllexport macro + +if(ANDROID) + # Support Android 15 16k page size (mirrors neoswarm_ffi) + target_link_options(gcs_ffi PRIVATE "-Wl,-z,max-page-size=16384") +endif() +``` + +**Pattern notes:** +- `PRIVATE` linkage on `gcs_core` (not `PUBLIC`) — `gcs_ffi` is a leaf; nothing links to it from C++. +- Export define uses `PRIVATE` scope so consumers see `dllimport`. +- Unlike the neoswarm plugin template (which sets `cmake_minimum_required(VERSION 3.10)` for Flutter plugin customers), GCS's `src/ffi/CMakeLists.txt` inherits the parent build's cmake version — do **not** re-declare `cmake_minimum_required` or `project()` here. + +--- + +### `src/ffi/gcs_core.h` (new — single opaque-handle C API) + +**Analog:** `GNUS-NEO-SWARM/src/genius_elm_chat_completions.h` (full file — 103 lines) + +**Export-macro + extern "C" guard** (lines 6-22): +```c +#if defined( _WIN32 ) +#if defined( GCS_FFI_EXPORTS ) +#define GCS_FFI_API __declspec( dllexport ) +#else +#define GCS_FFI_API __declspec( dllimport ) +#endif +#else +#define GCS_FFI_API +#endif + +#if defined( __cplusplus ) +#define GCS_FFI_NOEXCEPT noexcept +extern "C" +{ +#else +#define GCS_FFI_NOEXCEPT +#endif +``` + +**Header guard + stdint** (lines 1-4): +```c +#ifndef GENIUS_COGNITIVE_SYSTEM_GCS_CORE_H +#define GENIUS_COGNITIVE_SYSTEM_GCS_CORE_H + +#include +#include /* int64_t for Dart NativePort */ +``` + +**Opaque-handle session API skeleton** (style modeled on lines 37, 56, 64, 96 — note Doxygen `\brief`, `\param`, `\return`, and trailing `NOEXCEPT`): +```c + /** + * \brief Initialises a GCS core session. + * + * Creates the C++-side session object that owns GcsGlobalDb. Thread-safe: + * may be called multiple times; subsequent calls are no-ops returning the + * existing handle. + * + * \param dbPath Path for the CRDT store, or NULL for the default. + * \return Opaque session handle on success, NULL on failure. + */ + GCS_FFI_API GcsSession* gcs_init( const char* dbPath ) GCS_FFI_NOEXCEPT; + + GCS_FFI_API int gcs_join_topic( GcsSession* session, const char* topic ) GCS_FFI_NOEXCEPT; + GCS_FFI_API int gcs_publish( GcsSession* session, const char* topic, + const char* utf8Payload ) GCS_FFI_NOEXCEPT; + GCS_FFI_API int gcs_on_message( GcsSession* session, int64_t dartPort ) GCS_FFI_NOEXCEPT; + GCS_FFI_API void gcs_shutdown( GcsSession* session ) GCS_FFI_NOEXCEPT; + + /* Paired free for any heap strings returned by the API (mirrors GeniusElmStringFree) */ + GCS_FFI_API void gcs_string_free( char* value ) GCS_FFI_NOEXCEPT; +``` + +**Closing guard** (lines 98-102): +```c +#if defined( __cplusplus ) +} +#endif + +#endif // GENIUS_COGNITIVE_SYSTEM_GCS_CORE_H +``` + +**Pattern notes:** +- Opaque type: `typedef struct GcsSession GcsSession;` — declared only, defined in the .cpp. +- This is the **only** permitted `_WIN32` ifdef — the export-macro block. Project rule forbids OS ifdefs in logic; this is the established carve-out (confirmed in RESEARCH § Build System Wiring and CONTEXT § Established Patterns). +- The paired `gcs_string_free` export prevents Windows cross-allocator heap mismatch (per "Don't Hand-Roll" in RESEARCH). + +--- + +### `src/ffi/gcs_core_ffi.cpp` (new — FFI thunk) + +**Analog:** `GNUS-NEO-SWARM/src/genius_elm_chat_completions.cpp` (227 lines) + +**Includes + global state pattern** (lines 10-26): +```cpp +#include "gcs_core.h" + +#include "lib/gcs_core.hpp" // C++ session implementation + +#include +#include +#include +#include + +namespace +{ + std::mutex g_mutex; + // Opaque handle = pointer into this map. Session state owned here, not in Dart. + std::unique_ptr g_session; // Phase 1: single global session (mirrors g_server) +} +``` + +**Heap-string AllocCopy helper** (lines 28-38 — copy verbatim, rename only): +```cpp +char* AllocCopy( const std::string_view src ) +{ + const auto len = src.size(); + auto* dst = static_cast( std::malloc( len + 1 ) ); + if ( dst != nullptr ) + { + std::memcpy( dst, src.data(), len ); + dst[ len ] = '\0'; + } + return dst; +} +``` + +**extern "C" thunk with mutex + noexcept + null-tolerant** (lines 141-173, 210-225): +```cpp +extern "C" +{ + GCS_FFI_API GcsSession* gcs_init( const char* dbPath ) GCS_FFI_NOEXCEPT + { + std::lock_guard lock( g_mutex ); + + if ( g_session ) + { + return reinterpret_cast( g_session.get() ); // idempotent (mirrors line 148-151) + } + + gcs::CoreSession::Config cfg{}; + if ( dbPath != nullptr && dbPath[ 0 ] != '\0' ) + { + cfg.m_dbPath = dbPath; + } + + auto session = std::make_unique( std::move( cfg ) ); + auto result = session->Initialize(); + if ( !result.has_value() ) + { + return nullptr; // int → pointer return is the only semantic change vs analog + } + + g_session = std::move( session ); + return reinterpret_cast( g_session.get() ); + } + + GCS_FFI_API void gcs_string_free( char* value ) GCS_FFI_NOEXCEPT + { + std::free( value ); // copy of GeniusElmStringFree (line 210-213) + } + + GCS_FFI_API void gcs_shutdown( GcsSession* session ) GCS_FFI_NOEXCEPT + { + std::lock_guard lock( g_mutex ); + // Unregister dart_port BEFORE teardown (Pitfall 6 in RESEARCH). + // Shutdown ordering: clear port → Shutdown() joins io thread → free handle. + if ( g_session && reinterpret_cast( session ) == g_session.get() ) + { + g_session->UnregisterMessagePort(); + g_session->Shutdown(); + g_session.reset(); + } + } +} +``` + +**Pattern notes:** +- `g_session` is `unique_ptr` (not `shared_ptr`) per project ownership rule. +- Every FFI function: `std::lock_guard` first statement, `noexcept`, no exceptions escape. +- Null `const char*` from Dart is valid input (treated as "use default") — mirrors `GeniusElmInit(nullptr, nullptr)` semantics (line 154-161). + +--- + +### `test/CMakeLists.txt` (new — GCS root test registration) + +**Analog:** `GNUS-NEO-SWARM/test/CMakeLists.txt` (89 lines) + +**GTest discovery + helper macro pattern** (lines 1-53): +```cmake +find_package(GTest QUIET) + +if(NOT GTest_FOUND) + find_path(GTEST_INCLUDE_DIR gtest/gtest.h + PATHS ${THIRDPARTY_BUILD_DIR}/GTest/include + ${CMAKE_SOURCE_DIR}/../thirdparty/GTest/googletest/include + ) + find_library(GTEST_LIB gtest + PATHS ${THIRDPARTY_BUILD_DIR}/GTest/lib + ) + find_library(GTEST_MAIN_LIB gtest_main + PATHS ${THIRDPARTY_BUILD_DIR}/GTest/lib + ) + if(GTEST_INCLUDE_DIR AND GTEST_LIB) + add_library(GTest::GTest UNKNOWN IMPORTED) + set_target_properties(GTest::GTest PROPERTIES + IMPORTED_LOCATION ${GTEST_LIB} + INTERFACE_INCLUDE_DIRECTORIES ${GTEST_INCLUDE_DIR} + ) + add_library(GTest::Main UNKNOWN IMPORTED) + set_target_properties(GTest::Main PROPERTIES + IMPORTED_LOCATION ${GTEST_MAIN_LIB} + ) + set(GTest_FOUND TRUE) + endif() +endif() + +if(NOT GTest_FOUND) + message(WARNING "GTest not found — skipping tests") + return() +endif() + +enable_testing() + +# Helper macro +macro(gcs_test name sources libs) + add_executable(${name} ${sources}) + target_link_libraries(${name} PRIVATE + ${libs} + GTest::GTest + GTest::Main + ) + add_test(NAME ${name} COMMAND ${name}) +endmacro() + +gcs_test(test_gcs_core_smoke test_gcs_core_smoke.cpp "gcs_core;neoswarm_storage;sgns::crdt_globaldb") +gcs_test(test_gcs_ffi test_gcs_ffi.cpp "gcs_ffi;gcs_core") +``` + +**Pattern notes:** +- Rename macro `neoswarm_test` → `gcs_test`; drop the `SUPERGENIUS_TEST_DATA_DIR` define (not needed for smoke tests). +- No benchmark subdirectory this phase. +- Hook is already in place: `build/CommonBuildParameters.cmake:146-151` does `add_subdirectory(${PROJECT_ROOT}/test ...)` when `BUILD_TESTING` is on (RESEARCH § Validation Architecture). + +--- + +### `test/test_wait_condition.hpp` (new — copy verbatim) + +**Analog:** `GNUS-NEO-SWARM/test/storage/test_gcs_global_db.cpp:33-91` + +**Complete file** (extract + turn into header): +```cpp +/** + * @file test_wait_condition.hpp + * @brief Wait-condition template (condition_variable polling, no sleep_for). + * Copied verbatim from GNUS-NEO-SWARM/test/storage/test_gcs_global_db.cpp + * per project testing rule (no std::this_thread::sleep_for in tests). + * @date 2026-08-15 + */ + +#ifndef GCS_TEST_WAIT_CONDITION_HPP +#define GCS_TEST_WAIT_CONDITION_HPP + +#include +#include +#include +#include + +namespace gcs::test +{ + /// Upper bound for a single wait-condition call. + constexpr std::chrono::milliseconds kWaitTimeout{ 25000 }; + /// Re-check interval for pure polling predicates inside WaitForCondition. + constexpr std::chrono::milliseconds kPollInterval{ 10 }; + + /** + * @brief Poll `predicate` via condition_variable::wait_for until it returns true + * or `timeout` elapses. + * + * @param[in] predicate Nullary callable returning bool. + * @param[in] timeout Maximum time to wait. + * @return true if the predicate became true before the deadline; false otherwise. + */ + inline bool WaitForCondition( const std::function &predicate, + std::chrono::milliseconds timeout ) + { + std::mutex mtx; + std::condition_variable cv; + std::unique_lock lock( mtx ); + const auto deadline = std::chrono::steady_clock::now() + timeout; + while ( std::chrono::steady_clock::now() < deadline ) + { + if ( predicate() ) + { + return true; + } + const auto remaining = std::chrono::duration_cast( + deadline - std::chrono::steady_clock::now() ); + const auto slice = std::min( kPollInterval, remaining ); + cv.wait_for( lock, slice ); + } + return predicate(); + } +} // namespace gcs::test + +#endif // GCS_TEST_WAIT_CONDITION_HPP +``` + +**Pattern notes:** +- Moved out of anonymous namespace into `gcs::test` so both smoke tests can include it. +- `inline` on the function so multiple TUs can include the header without ODR violations. + +--- + +### `test/test_gcs_core_smoke.cpp` (new — GlobalDB lifecycle + CRDT round-trip) + +**Analog:** `GNUS-NEO-SWARM/test/storage/test_gcs_global_db.cpp` (full 249 lines — copy structural skeleton verbatim, rename namespaces) + +**File-header + includes block** (lines 1-31): +```cpp +/** + * @file test_gcs_core_smoke.cpp + * @brief Phase 1 smoke tests for the gcs_core session: GlobalDB lifecycle via the + * injected-pubsub seam, AddBroadcastTopic+AddListenTopic on a gcs/chat/* + * topic, and a Put→Get round-trip over a real GossipPubSub on port 0. + * Uses the GCS wait-condition template (no sleep_for). + * @date 2026-08-15 + */ + +#include "lib/gcs_core.hpp" +#include "test_wait_condition.hpp" + +#include +#include +#include +#include + +#include + +#include "crdt/globaldb/keypair_file_storage.hpp" +#include "ipfs_pubsub/gossip_pubsub.hpp" + +#include +#include + +#include +#include +``` + +**Soralog one-time setup** (lines 46-58 + 108-117 — REQUIRED, otherwise "Logging system is not ready" crash): +```cpp +namespace +{ + /// GossipPubSub bind address used by every test. + constexpr const char *kListenIp = "0.0.0.0"; + + /// Minimal soralog YAML — console sink only, error level. + constexpr const char *kLoggingYaml = R"( + sinks: + - name: console + type: console + color: false + groups: + - name: gcs_core_smoke_test + sink: console + level: error + children: + - name: libp2p + - name: Gossip + )"; +} + +// Inside fixture: +static void SetUpTestSuite() +{ + auto loggerConfigurator = std::make_shared(); + auto configFromYaml = std::make_shared( + loggerConfigurator, std::string{ kLoggingYaml } ); + auto loggingSystem = std::make_shared( configFromYaml ); + const auto confResult = loggingSystem->configure(); + ASSERT_FALSE( confResult.has_error ) << "Could not configure test logging system"; + libp2p::log::setLoggingSystem( loggingSystem ); +} +``` + +**Per-test temp dir + TearDown** (lines 119-134): +```cpp +void SetUp() override +{ + const auto *info = ::testing::UnitTest::GetInstance()->current_test_info(); + const auto uniqueSalt = std::chrono::steady_clock::now().time_since_epoch().count(); + m_tempPath = ( std::filesystem::temp_directory_path() + / ( std::string{ "gcs_core_smoke_" } + info->name() + "_" + + std::to_string( uniqueSalt ) ) ) + .string(); + std::filesystem::create_directories( m_tempPath ); +} + +void TearDown() override +{ + std::error_code ec; + std::filesystem::remove_all( m_tempPath, ec ); +} +``` + +**GossipPubSub bring-up helper** (lines 140-158 — port 0 ephemeral): +```cpp +std::shared_ptr MakeStartedPubSub( const std::string &keyDir ) +{ + sgns::crdt::KeyPairFileStorage keyStore( keyDir ); + auto keyPairResult = keyStore.GetKeyPair(); + EXPECT_FALSE( keyPairResult.has_error() ); + if ( keyPairResult.has_error() ) + { + return nullptr; + } + auto pubsub = std::make_shared( keyPairResult.value() ); + auto startFuture = pubsub->Start( 0, {}, kListenIp, {} ); // port 0 = ephemeral + auto startError = startFuture.get(); + EXPECT_FALSE( startError ) << "Could not start GossipPubSub: " << startError.message(); + if ( startError ) + { + return nullptr; + } + return pubsub; +} +``` + +**Lifecycle test shape** (lines 200-222 — LifecycleWithInjectedPubSub): +```cpp +TEST_F( GcsCoreSmokeTest, LifecycleWithInjectedPubSub ) +{ + auto pubsub = MakeStartedPubSub( m_tempPath + "/key" ); + ASSERT_NE( pubsub, nullptr ); + + gcs::CoreSession::Config cfg{}; + cfg.m_dbPath = m_tempPath + "/db"; + gcs::CoreSession session( cfg ); + + auto res = session.Initialize( pubsub ); + ASSERT_TRUE( res.has_value() ); + EXPECT_TRUE( session.IsRunning() ); + + // The db directory may be created lazily — wait via the wait-condition template. + const auto dbPath = cfg.m_dbPath; + EXPECT_TRUE( gcs::test::WaitForCondition( + [&dbPath]() { return std::filesystem::exists( dbPath ); }, + gcs::test::kWaitTimeout ) ); + + session.Shutdown(); + EXPECT_FALSE( session.IsRunning() ); + + pubsub->Stop(); +} +``` + +**CRDT round-trip test shape** (extends LifecycleWithInjectedPubSub with topic + Put/Get): +```cpp +TEST_F( GcsCoreSmokeTest, CrdtPutGetRoundTripsOnGcsChatTopic ) +{ + auto pubsub = MakeStartedPubSub( m_tempPath + "/key" ); + ASSERT_NE( pubsub, nullptr ); + + gcs::CoreSession::Config cfg{}; + cfg.m_dbPath = m_tempPath + "/db"; + gcs::CoreSession session( cfg ); + ASSERT_TRUE( session.Initialize( pubsub ).has_value() ); + + // Topic name follows architecture convention gcs/chat/ (CONTEXT.md + // canonical_refs → gcs-chat-architecture.md). + constexpr const char *kSmokeTopic = "gcs/chat/smoke-test"; + ASSERT_TRUE( session.AddBroadcastTopic( kSmokeTopic ).has_value() ); + ASSERT_TRUE( session.AddListenTopic( kSmokeTopic ).has_value() ); + + // Put→Get round-trip via the GcsGlobalDb pass-through accessors added in the + // submodule task (gcs_global_db.hpp/cpp — see "GNUS-NEO-SWARM submodule" below). + const std::string key = "smoke-key"; + const std::string value = "smoke-value"; + ASSERT_TRUE( session.Put( key, value ).has_value() ); + + auto getResult = session.Get( key ); + ASSERT_TRUE( getResult.has_value() ); + EXPECT_EQ( getResult.value(), value ); + + session.Shutdown(); + pubsub->Stop(); +} +``` + +**Pattern notes:** +- Tests use the injected-pubsub seam — never bring GeniusNode online in CI (RESEARCH § Alternatives Considered). +- The `SetUpTestSuite` soralog block is **mandatory** — omitting it crashes the first GossipPubSub construction (RESEARCH Pitfall 2). +- No `std::this_thread::sleep_for` anywhere — always `WaitForCondition`. + +--- + +### `test/test_gcs_ffi.cpp` (new — FFI init/echo/shutdown) + +**Analog:** `GNUS-NEO-SWARM/test/ffi/test_genius_elm_ffi.cpp` (100+ lines) + +**Fixture with shutdown-on-teardown** (lines 10-22): +```cpp +/** + * @file test_gcs_ffi.cpp + * @brief Unit tests for the gcs_ffi C ABI — init, echo, string_free, shutdown. + * @date 2026-08-15 + */ + +#include "ffi/gcs_core.h" +#include + +class GcsFFI : public ::testing::Test +{ +protected: + void TearDown() override + { + // Destroy the C++ session before GTest global teardown — the GcsGlobalDb + // destructor touches asio::io_context internals and the libp2p stack; if left + // to static destruction after __cxa_finalize, pthread primitives are gone + // and a "pthread lock: Invalid argument" abort fires at process exit. + // (Same reasoning as GeniusElmFFI::TearDown in the analog.) + gcs_shutdown( /* handle from test state */ ); + } +}; +``` + +**Test shapes** (mirroring lines 24-94 of analog): +```cpp +TEST_F( GcsFFI, InitWithNullptrUsesDefaults ) +{ + GcsSession* h = gcs_init( nullptr ); + EXPECT_NE( h, nullptr ); + // handle stored in fixture for TearDown +} + +TEST_F( GcsFFI, StringFreeNullptrDoesNotCrash ) +{ + gcs_string_free( nullptr ); + SUCCEED(); +} + +TEST_F( GcsFFI, MultipleInitCallsReturnSameHandle ) +{ + GcsSession* h1 = gcs_init( nullptr ); + GcsSession* h2 = gcs_init( nullptr ); + GcsSession* h3 = gcs_init( nullptr ); + EXPECT_EQ( h1, h2 ); + EXPECT_EQ( h2, h3 ); +} + +TEST_F( GcsFFI, ShutdownNullptrIsSafeNoOp ) +{ + gcs_shutdown( nullptr ); + SUCCEED(); +} +``` + +**Pattern notes:** +- Shutdown in `TearDown` is mandatory (prevents pthread abort at process exit — analog's `FIX-01` comment). +- FFI tests link `gcs_ffi` only (not `gcs_core` directly); they exercise the C ABI exactly as Dart will. + +--- + +### `.github/workflows/cmake.yml` (new — CI) + +**Analog:** `../GeniusSDK/.github/workflows/cmake.yml` (594 lines — copy near-verbatim with documented deltas) + +**Trigger + permissions block** (lines 1-26 — copy verbatim): +```yaml +name: Release Build CI + +on: + push: + branches: + - develop + - main + paths-ignore: + - ".github/**" + - "Readme.md" + pull_request: + branches: + - develop + - main + paths-ignore: + - ".github/**" + - "Readme.md" + workflow_dispatch: + inputs: + tag: + description: "Release tag" + required: false + type: string +permissions: + contents: write + packages: read +``` + +**resolve-runners job** (lines 28-125 — copy verbatim, no changes): +The full `pick_runner()` bash function with `sg-ubuntu-linux` / `sg-arm-linux` / `SG-WIN11` / `gv-OSX-Large` selectors and `ubuntu-latest` / `ubuntu-24.04-arm` / `windows-2022` / `macos-latest` fallbacks. Uses `secrets.GNUS_TOKEN_1`. ~100 lines. + +**Matrix block** (lines 138-191 — copy verbatim): +5 targets × Debug/Release with ABI includes for Linux (x86_64, aarch64) and Android (arm64-v8a, armeabi-v7a), excludes for empty-ABI Linux/Android. Container `ghcr.io/geniusventures/debian-bullseye:latest` on Linux/Android. + +**Self-hosted cleanup step** (lines 214-267 — copy verbatim, change one path): +```yaml + - name: Clean workspace (self-hosted runners) + if: ${{ runner.environment == 'self-hosted' }} + shell: bash + run: | + # ... (git clean / reset / rm -rf) ... + for dir in thirdparty zkLLVM SuperGenius GeniusSDK; do + if [ -d "$dir" ]; then + echo "Removing $dir directory..." + $SUDO rm -rf "$dir" + fi + done +``` +Keep the `zkLLVM` cleanup line even though GCS skips the explicit zkLLVM download step — stale dirs from prior runs could exist on the self-hosted runner. + +**Checkout step** (lines 269-274 — modify path): +```yaml + - name: Checkout GCS + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd + with: + # No `path:` override — workspace IS the GCS repo root. + submodules: "recursive" # GNUS-NEO-SWARM, build (cmaketemplate), gendoc-template, src/app/scaffold + persist-credentials: true +``` + +**thirdparty release download** (lines 286-353 — copy verbatim, no changes): +Tag format `[-]--`, asset `[-]-.tar.gz`, sets `THIRDPARTY_BUILD_DIR`. + +**zkLLVM release download** (lines 355-409 — DELETE entire step per D-09): +Per RESEARCH § CI/CD item 3, cmaketemplate will still auto-download zkLLVM headers at configure time. "Skip zkLLVM" means no explicit step, no assigner binaries. Confirm with user if "zero zkLLVM bytes" was intended. + +**SuperGenius release download** (lines 411-450 — copy verbatim, no changes): +Sets `SUPERGENIUS_DIR`. + +**NEW: GeniusSDK release download** (insert after SuperGenius step — GCS consumes GeniusSDK prebuilt, unlike GeniusSDK repo which builds itself): +```yaml + - name: Download GeniusSDK + shell: bash + run: | + if [ "$IS_TAG" == "true" ]; then + sdk_branch="$CURRENT_BRANCH" + elif [ "$CURRENT_BRANCH" == "main" ]; then + sdk_branch="main" + else + sdk_branch="develop" + fi + + mkdir GeniusSDK + cd GeniusSDK + + if [ "$IS_TAG" == "true" ]; then + tag_name="${sdk_branch}" + else + if [ '${{matrix.abi}}' ]; then + tag_name="${{matrix.target}}-${{matrix.abi}}-${sdk_branch}-${{matrix.build-type}}" + else + tag_name="${{matrix.target}}-${sdk_branch}-${{matrix.build-type}}" + fi + fi + + gh release download ${tag_name} --repo GeniusVentures/GeniusSDK -p "${FILE_NAME}" + tar -xzf "${FILE_NAME}" + + if [ "${{ matrix.target }}" = "Windows" ]; then + echo "GENIUS_SDK_DIR=$(pwd -W)" >> $GITHUB_ENV + else + echo "GENIUS_SDK_DIR=$(pwd)" >> $GITHUB_ENV + fi +``` + +**Per-platform host setup steps** (lines 452-502 — copy verbatim): +- Linux: `update-alternatives` for clang + `apt install ccache ninja-build libvulkan-dev libzstd-dev libsecret-1-dev -y` +- Windows: `choco install ccache -A` +- macOS: `brew install ninja bash gnu-tar` + GNU tar PATH fixup +- Android: NDK r27b download + rustup targets (drop the `rustup` lines if GCS has no Rust — **confirm with user**) + +**CMake configure steps** (lines 504-527 — copy verbatim, drop `-DZKLLVM_BUILD_DIR`, drop `-DSUPERGENIUS_DIR` since GCS uses sibling auto-detect, **add `-DGENIUSSDK_BUILD_DIR` if needed**; working directory becomes `${{github.workspace}}` since workspace IS GCS root): +```yaml + - name: Configure CMake for Mac + if: ${{ matrix.target == 'OSX'}} + working-directory: ${{github.workspace}} + run: cmake -S build/${{matrix.target}} -B $BUILD_DIRECTORY -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -DTHIRDPARTY_BUILD_DIR=${{env.THIRDPARTY_BUILD_DIR}} +``` +Same shape for Linux/Windows/Android/iOS. The `PROJECT_SUPER_ROOT` auto-detect resolves SuperGenius and GeniusSDK as siblings of the workspace (RESEARCH § CI/CD item 4). + +**Build + Install** (lines 529-536 — copy verbatim, working-directory becomes `${{github.workspace}}/${{env.BUILD_DIRECTORY}}`). + +**ctest steps** (copy from `../SuperGenius/.github/workflows/cmake.yml:703-740` — NOT in the GeniusSDK workflow): +```yaml + - name: Run tests (Linux) + working-directory: ${{ github.workspace }}/${{ env.BUILD_DIRECTORY }} + if: ${{ matrix.target == 'Linux' }} + shell: bash + run: | + dbus-run-session -- bash -c '\ + mkdir -p ~/.cache ~/.local/share/keyrings && \ + echo -n "login" > ~/.local/share/keyrings/default && \ + echo "[keyring] + display-name=login + lock-on-idle=false + lock-after=false" > ~/.local/share/keyrings/login.keyring && \ + eval $(printf "\n" | gnome-keyring-daemon --unlock --components=secrets) && \ + ctest . -j -C ${{ matrix.build-type }} --output-on-failure' + + - name: Run tests (OSX) + working-directory: ${{ github.workspace }}/${{ env.BUILD_DIRECTORY }} + if: ${{ matrix.target == 'OSX' }} + run: ctest . -j -C ${{ matrix.build-type }} --output-on-failure + + - name: Run tests (Windows) + working-directory: ${{ github.workspace }}/${{ env.BUILD_DIRECTORY }} + if: ${{ matrix.build-type == 'Release' && matrix.target == 'Windows' }} + run: ctest . -j -C ${{ matrix.build-type }} --output-on-failure +``` +**No ctest on Android/iOS** (cross-compiles). The gnome-keyring wrapper is harmless for GCS (per RESEARCH § A4) and matches SuperGenius precedent — copy it verbatim. + +**Release upload steps** (lines 538-593 — copy verbatim, replace `GeniusSDK` paths with GCS workspace; the `--transform 's|^|${{env.BUILD_DIRECTORY}}/|'` tar argument needs the right top-level dir for GCS — likely `src/` + `GNUS-NEO-SWARM/` or whatever the planner decides to ship. **Planner's discretion**; recommend including this phase to smoke-prove artifact packaging per RESEARCH § CI/CD item 8). + +**Pattern notes:** +- Total ~600 lines, 95% copy from GeniusSDK template. +- Deltas are: drop zkLLVM step, add GeniusSDK download step, change `working-directory` from `${{github.workspace}}/GeniusSDK` to `${{github.workspace}}`, add ctest steps from SuperGenius workflow. +- zkLLVM cleanup line stays in self-hosted cleanup step. + +--- + +### `GNUS-NEO-SWARM/src/storage/gcs_global_db.{hpp,cpp}` (submodule — pass-through accessors) + +**Analog:** SuperGenius `src/crdt/globaldb/globaldb.hpp:89-225` (the underlying API the accessors wrap) + +**Addition shape** (in `GcsGlobalDb` class — placed after `IsRunning()`): +```cpp + /** + * @brief Add a broadcast topic to the underlying GlobalDB. + * + * Pass-through to sgns::crdt::GlobalDB::AddBroadcastTopic. Required for + * Phase 1 CORE-05 smoke test (gcs/chat/ topic wiring). + * + * @param[in] topicName Topic name (e.g. "gcs/chat/smoke-test"). + * @return outcome::success or Error::GcsDbError if not running. + */ + outcome::result AddBroadcastTopic( const std::string &topicName ); + + /** + * @brief Add a listen topic to the underlying GlobalDB. + */ + outcome::result AddListenTopic( const std::string &topicName ); + + /** + * @brief Put a key/value pair into the CRDT store. + * + * @param[in] key HierarchicalKey path. + * @param[in] value Buffer contents. + */ + outcome::result Put( const std::string &key, const std::string &value ); + + /** + * @brief Get a value from the CRDT store. + */ + outcome::result Get( const std::string &key ); +``` + +**Implementation shape** (thin wrappers over `m_db`, in the .cpp): +```cpp +outcome::result GcsGlobalDb::AddBroadcastTopic( const std::string &topicName ) +{ + if ( !m_running.load() ) + { + return outcome::failure( Error::GcsDbError ); + } + return m_db->AddBroadcastTopic( topicName ); +} +// ... same shape for AddListenTopic / Put / Get +``` + +**Pattern notes:** +- This is a submodule change — must be committed on a GNUS-NEO-SWARM branch, NOT in the GCS repo directly. +- Per RESEARCH Open Question 2, planner should sequence this as its own task with its own neoswarm-branch commit before the GCS smoke test task. +- Confirm scope with the user before adding — STATE.md flags "Phase 1 depends on GlobalDB CRDT integration from GNUS-NEO-SWARM Phase 3" as a concern. +- Match `GcsGlobalDb`'s existing error mapping (D-14): use `Error::GcsDbError` for all failures (not running + underlying errors). + +--- + +## Shared Patterns + +### Hard-Required Dependency Linkage (no stubs) + +**Source:** `GNUS-NEO-SWARM/src/storage/CMakeLists.txt:14-18, 30-47` +**Apply to:** `src/CMakeLists.txt` (gcs_core → neoswarm_storage), `src/ffi/CMakeLists.txt` (gcs_ffi → gcs_core) + +```cmake +if(TARGET ) + target_link_libraries( PUBLIC ) +else() + message(FATAL_ERROR " target not found — must be configured first") +endif() +``` + +Project Conditional Compilation rule: missing required libraries fail at CMake configure time, never degrade to stub. No `#ifdef` feature gates in source. + +--- + +### Export Macro Idiom (FFI shared lib) + +**Source:** `GNUS-NEO-SWARM/src/genius_elm_chat_completions.h:6-22` +**Apply to:** `src/ffi/gcs_core.h` + +```c +#if defined( _WIN32 ) +#if defined( GCS_FFI_EXPORTS ) +#define GCS_FFI_API __declspec( dllexport ) +#else +#define GCS_FFI_API __declspec( dllimport ) +#endif +#else +#define GCS_FFI_API +#endif + +#if defined( __cplusplus ) +#define GCS_FFI_NOEXCEPT noexcept +extern "C" +{ +#else +#define GCS_FFI_NOEXCEPT +#endif +``` + +The `_WIN32` ifdef here is the **only** permitted OS preprocessor guard in the GCS C++ source. It is the established in-repo carve-out for FFI export headers (RESEARCH § Build System Wiring). + +--- + +### Heap-String Allocation + Paired Free + +**Source:** `GNUS-NEO-SWARM/src/genius_elm_chat_completions.cpp:28-38, 210-213` +**Apply to:** `src/ffi/gcs_core_ffi.cpp` + +```cpp +char* AllocCopy( const std::string_view src ) +{ + const auto len = src.size(); + auto* dst = static_cast( std::malloc( len + 1 ) ); + if ( dst != nullptr ) + { + std::memcpy( dst, src.data(), len ); + dst[ len ] = '\0'; + } + return dst; +} +// ... +GCS_FFI_API void gcs_string_free( char* value ) GCS_FFI_NOEXCEPT +{ + std::free( value ); +} +``` + +Prevents Windows cross-allocator heap mismatch (RESEARCH § Don't Hand-Roll). + +--- + +### FFI Thread-Safety + Idempotent Init + +**Source:** `GNUS-NEO-SWARM/src/genius_elm_chat_completions.cpp:23-26, 146-152, 220-225` +**Apply to:** `src/ffi/gcs_core_ffi.cpp` + +```cpp +namespace +{ + std::mutex g_mutex; + std::unique_ptr g_session; +} + +// Every FFI function: +std::lock_guard lock( g_mutex ); +if ( g_session ) { return /* existing */; } // idempotent +``` + +Global mutex + `unique_ptr` global. Subsequent init calls are no-ops. All functions `noexcept` (project rule: no exceptions across FFI). + +--- + +### Wait-Condition Test Template (no sleep_for) + +**Source:** `GNUS-NEO-SWARM/test/storage/test_gcs_global_db.cpp:72-90` +**Apply to:** All GCS test files (extracted into `test/test_wait_condition.hpp`) + +```cpp +bool WaitForCondition( const std::function &predicate, std::chrono::milliseconds timeout ) +{ + std::mutex mtx; + std::condition_variable cv; + std::unique_lock lock( mtx ); + const auto deadline = std::chrono::steady_clock::now() + timeout; + while ( std::chrono::steady_clock::now() < deadline ) + { + if ( predicate() ) { return true; } + const auto remaining = std::chrono::duration_cast( + deadline - std::chrono::steady_clock::now() ); + const auto slice = std::min( kPollInterval, remaining ); + cv.wait_for( lock, slice ); + } + return predicate(); +} +``` + +Project rule: NEVER `std::this_thread::sleep_for` in tests. Always `WaitForCondition`. + +--- + +### Soralog One-Time Setup in Tests + +**Source:** `GNUS-NEO-SWARM/test/storage/test_gcs_global_db.cpp:46-58, 108-117` +**Apply to:** All GCS test fixtures that construct `GossipPubSub` or touch SuperGenius loggers + +```cpp +static void SetUpTestSuite() +{ + auto loggerConfigurator = std::make_shared(); + auto configFromYaml = std::make_shared( + loggerConfigurator, std::string{ kLoggingYaml } ); + auto loggingSystem = std::make_shared( configFromYaml ); + ASSERT_FALSE( loggingSystem->configure().has_error ); + libp2p::log::setLoggingSystem( loggingSystem ); +} +``` + +Omitting this crashes `GossipPubSub` construction with "Logging system is not ready" (RESEARCH Pitfall 2). + +--- + +### Injected-Pubsub Test Seam + +**Source:** `GNUS-NEO-SWARM/src/storage/gcs_global_db.hpp:113-123` + `test_gcs_global_db.cpp:140-158` +**Apply to:** `test/test_gcs_core_smoke.cpp`, `src/lib/gcs_core.cpp` (CoreSession must expose the same overload) + +```cpp +// Production path: +outcome::result Initialize(); // uses GeniusSDKGetNode() + +// Test seam (Tier 2 fixture pattern): +outcome::result Initialize( std::shared_ptr pubsub ); +``` + +Tests stand up `GossipPubSub` on port 0 (ephemeral) — never bring GeniusNode online in CI (RESEARCH § Alternatives Considered). + +--- + +### Outcome-Based Error Handling + +**Source:** `GNUS-NEO-SWARM/src/storage/gcs_global_db.hpp:110, 123` + `gcs_global_db.cpp` +**Apply to:** `src/lib/gcs_core.cpp`, `src/ffi/gcs_core_ffi.cpp` (translate `outcome::result` → C return codes at FFI boundary) + +Every fallible C++ function returns `outcome::result`. Error codes from `GNUS-NEO-SWARM/src/common/error.hpp` (`Error::SdkNotInitialized`, `Error::GcsDbError`, etc.). At the FFI boundary, translate `outcome::failure(...)` → `int` status code or `nullptr` handle; never let exceptions escape. + +--- + +### C++ Style (SuperGenius Naming) + +**Source:** `GNUS-NEO-SWARM/CLAUDE.md` (overrides general code style) +**Apply to:** All new GCS C++ files + +| Element | Convention | Example | +|---------|-----------|---------| +| Member variables | `m_` prefix + camelCase | `m_dbPath`, `m_logger`, `m_ioThread` | +| Function arguments | camelCase | `topicName`, `dbPath` | +| File names | `snake_case` | `gcs_core.cpp`, `gcs_core_ffi.cpp` | +| Constants (compile-time) | `k` prefix + PascalCase | `kWaitTimeout`, `kListenIp`, `kDefaultDbPath` | +| Accessors | `Get`/`Set`/`Is` prefix | `IsRunning()`, `GetSession()` | +| Braces | Allman (own line) | see all analog excerpts above | +| Spacing | `if ( condition )` (space inside parens) | see all analog excerpts above | +| Line length | 120 chars max | — | +| Doxygen | `\brief` / `\param[in]` / `\return` on every public API | see `genius_elm_chat_completions.h` | + +C++17 ceiling — no coroutines, no `std::format`, no concepts. + +--- + +## No Analog Found + +Files with no close match in the codebase (planner should use RESEARCH.md patterns instead): + +| File | Role | Data Flow | Reason | +|------|------|-----------|--------| +| `src/ffi/dart_native_api.h` + `dart_api_dl.c` (vendored Dart SDK headers — only if the spike confirms this include strategy) | FFI C header | event-driven (native→Dart callback) | No `Dart_PostCObject`/`Dart_Port`/`NativePort`/`ReceivePort` usage anywhere in GNUS-NEO-SWARM, SuperGenius, or GeniusSDK (RESEARCH § FFI Callback Mechanism — repo-wide grep returned zero hits). The standard Dart API_DL pattern is documented in RESEARCH § Pattern 1 with example code; planner should budget a small spike task to validate the include/link strategy. | + +This is the only genuine "no in-repo precedent" gap in the phase. RESEARCH § FFI Callback Mechanism documents the standard pattern with example C and Dart code; CONTEXT D-05 locks the ReceivePort/NativePort approach. + +--- + +## Metadata + +**Analog search scope:** +- `GNUS-NEO-SWARM/src/storage/` (GcsGlobalDb component + CMakeLists) +- `GNUS-NEO-SWARM/src/genius_elm_chat_completions.{h,cpp}` (FFI export idiom) +- `GNUS-NEO-SWARM/neoswarm_ffi/` (ffigen plugin structure) +- `GNUS-NEO-SWARM/test/{CMakeLists.txt,storage/,ffi/}` (test patterns) +- `../GeniusSDK/.github/workflows/cmake.yml` (consumer-repo CI template) +- `../SuperGenius/.github/workflows/cmake.yml` (ctest steps reference) +- `src/{CMakeLists.txt,lib/gcs_core.cpp}` (current GCS state) + +**Files scanned:** 12 (read in full or targeted sections) +**Pattern extraction date:** 2026-08-15 +**Cross-references:** RESEARCH.md (all cited line numbers verified), CONTEXT.md (D-01 through D-09 traced to patterns) From 346c64626cf7431275d87f28342b5502b1876719 Mon Sep 17 00:00:00 2001 From: Super Genius Date: Mon, 17 Aug 2026 13:39:45 -0700 Subject: [PATCH 16/49] wip for app-restructure --- .gitmodules | 6 +++--- .idea/vcs.xml | 1 + .planning/active-workstream | 2 +- app/src/scaffold | 1 - src/CMakeLists.txt | 2 +- {app/src => src/app}/.gitignore | 0 {app/src => src/app}/.metadata | 0 {app/src => src/app}/README.md | 0 {app/src => src/app}/analysis_options.yaml | 0 {app/src => src/app}/android/.gitignore | 0 {app/src => src/app}/android/app/build.gradle.kts | 0 .../app}/android/app/src/debug/AndroidManifest.xml | 0 .../app}/android/app/src/main/AndroidManifest.xml | 0 .../ai/gnus/flutter/flutter_app/MainActivity.kt | 0 .../src/main/res/drawable-v21/launch_background.xml | 0 .../app/src/main/res/drawable/launch_background.xml | 0 .../app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../app/src/main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../app/src/main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../app/src/main/res/values-night/styles.xml | 0 .../app}/android/app/src/main/res/values/styles.xml | 0 .../android/app/src/profile/AndroidManifest.xml | 0 {app/src => src/app}/android/build.gradle.kts | 0 {app/src => src/app}/android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 {app/src => src/app}/android/settings.gradle.kts | 0 {app/src => src/app}/ios/.gitignore | 0 .../app}/ios/Flutter/AppFrameworkInfo.plist | 0 {app/src => src/app}/ios/Flutter/Debug.xcconfig | 0 {app/src => src/app}/ios/Flutter/Release.xcconfig | 0 {app/src => src/app}/ios/Podfile | 0 .../app}/ios/Runner.xcodeproj/project.pbxproj | 0 .../project.xcworkspace/contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../ios/Runner.xcworkspace/contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 {app/src => src/app}/ios/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/Icon-App-1024x1024@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin .../AppIcon.appiconset/Icon-App-83.5x83.5@2x.png | Bin .../LaunchImage.imageset/Contents.json | 0 .../LaunchImage.imageset/LaunchImage.png | Bin .../LaunchImage.imageset/LaunchImage@2x.png | Bin .../LaunchImage.imageset/LaunchImage@3x.png | Bin .../Assets.xcassets/LaunchImage.imageset/README.md | 0 .../ios/Runner/Base.lproj/LaunchScreen.storyboard | 0 .../app}/ios/Runner/Base.lproj/Main.storyboard | 0 {app/src => src/app}/ios/Runner/Info.plist | 0 .../app}/ios/Runner/Runner-Bridging-Header.h | 0 .../app}/ios/RunnerTests/RunnerTests.swift | 0 {app/src => src/app}/lib/main.dart | 0 {app/src => src/app}/linux/.gitignore | 0 {app/src => src/app}/linux/CMakeLists.txt | 2 +- {app/src => src/app}/linux/flutter/CMakeLists.txt | 0 .../linux/flutter/generated_plugin_registrant.cc | 0 .../linux/flutter/generated_plugin_registrant.h | 0 .../app}/linux/flutter/generated_plugins.cmake | 0 {app/src => src/app}/linux/runner/CMakeLists.txt | 0 {app/src => src/app}/linux/runner/main.cc | 0 {app/src => src/app}/linux/runner/my_application.cc | 0 {app/src => src/app}/linux/runner/my_application.h | 0 {app/src => src/app}/macos/.gitignore | 0 .../app}/macos/Flutter/Flutter-Debug.xcconfig | 0 .../app}/macos/Flutter/Flutter-Release.xcconfig | 0 .../macos/Flutter/GeneratedPluginRegistrant.swift | 0 {app/src => src/app}/macos/Podfile | 0 {app/src => src/app}/macos/Podfile.lock | 0 .../app}/macos/Runner.xcodeproj/project.pbxproj | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../Runner.xcworkspace/contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 {app/src => src/app}/macos/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/app_icon_1024.png | Bin .../AppIcon.appiconset/app_icon_128.png | Bin .../AppIcon.appiconset/app_icon_16.png | Bin .../AppIcon.appiconset/app_icon_256.png | Bin .../AppIcon.appiconset/app_icon_32.png | Bin .../AppIcon.appiconset/app_icon_512.png | Bin .../AppIcon.appiconset/app_icon_64.png | Bin .../app}/macos/Runner/Base.lproj/MainMenu.xib | 0 .../app}/macos/Runner/Configs/AppInfo.xcconfig | 0 .../app}/macos/Runner/Configs/Debug.xcconfig | 0 .../app}/macos/Runner/Configs/Release.xcconfig | 0 .../app}/macos/Runner/Configs/Warnings.xcconfig | 0 .../app}/macos/Runner/DebugProfile.entitlements | 0 {app/src => src/app}/macos/Runner/Info.plist | 0 .../app}/macos/Runner/MainFlutterWindow.swift | 0 .../app}/macos/Runner/Release.entitlements | 0 .../app}/macos/RunnerTests/RunnerTests.swift | 0 {app/src => src/app}/pubspec.lock | 0 {app/src => src/app}/pubspec.yaml | 0 src/app/scaffold | 1 + {app/src => src/app}/test/widget_test.dart | 0 {app/src => src/app}/windows/.gitignore | 0 {app/src => src/app}/windows/CMakeLists.txt | 2 +- {app/src => src/app}/windows/flutter/CMakeLists.txt | 0 .../windows/flutter/generated_plugin_registrant.cc | 0 .../windows/flutter/generated_plugin_registrant.h | 0 .../app}/windows/flutter/generated_plugins.cmake | 0 {app/src => src/app}/windows/runner/CMakeLists.txt | 0 {app/src => src/app}/windows/runner/Runner.rc | 0 .../app}/windows/runner/flutter_window.cpp | 0 .../src => src/app}/windows/runner/flutter_window.h | 0 {app/src => src/app}/windows/runner/main.cpp | 0 {app/src => src/app}/windows/runner/resource.h | 0 .../app}/windows/runner/resources/app_icon.ico | Bin .../app}/windows/runner/runner.exe.manifest | 0 {app/src => src/app}/windows/runner/utils.cpp | 0 {app/src => src/app}/windows/runner/utils.h | 0 .../src => src/app}/windows/runner/win32_window.cpp | 0 {app/src => src/app}/windows/runner/win32_window.h | 0 131 files changed, 9 insertions(+), 8 deletions(-) delete mode 160000 app/src/scaffold rename {app/src => src/app}/.gitignore (100%) rename {app/src => src/app}/.metadata (100%) rename {app/src => src/app}/README.md (100%) rename {app/src => src/app}/analysis_options.yaml (100%) rename {app/src => src/app}/android/.gitignore (100%) rename {app/src => src/app}/android/app/build.gradle.kts (100%) rename {app/src => src/app}/android/app/src/debug/AndroidManifest.xml (100%) rename {app/src => src/app}/android/app/src/main/AndroidManifest.xml (100%) rename {app/src => src/app}/android/app/src/main/kotlin/ai/gnus/flutter/flutter_app/MainActivity.kt (100%) rename {app/src => src/app}/android/app/src/main/res/drawable-v21/launch_background.xml (100%) rename {app/src => src/app}/android/app/src/main/res/drawable/launch_background.xml (100%) rename {app/src => src/app}/android/app/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename {app/src => src/app}/android/app/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename {app/src => src/app}/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename {app/src => src/app}/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {app/src => src/app}/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {app/src => src/app}/android/app/src/main/res/values-night/styles.xml (100%) rename {app/src => src/app}/android/app/src/main/res/values/styles.xml (100%) rename {app/src => src/app}/android/app/src/profile/AndroidManifest.xml (100%) rename {app/src => src/app}/android/build.gradle.kts (100%) rename {app/src => src/app}/android/gradle.properties (100%) rename {app/src => src/app}/android/gradle/wrapper/gradle-wrapper.properties (100%) rename {app/src => src/app}/android/settings.gradle.kts (100%) rename {app/src => src/app}/ios/.gitignore (100%) rename {app/src => src/app}/ios/Flutter/AppFrameworkInfo.plist (100%) rename {app/src => src/app}/ios/Flutter/Debug.xcconfig (100%) rename {app/src => src/app}/ios/Flutter/Release.xcconfig (100%) rename {app/src => src/app}/ios/Podfile (100%) rename {app/src => src/app}/ios/Runner.xcodeproj/project.pbxproj (100%) rename {app/src => src/app}/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata (100%) rename {app/src => src/app}/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {app/src => src/app}/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename {app/src => src/app}/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename {app/src => src/app}/ios/Runner.xcworkspace/contents.xcworkspacedata (100%) rename {app/src => src/app}/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {app/src => src/app}/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename {app/src => src/app}/ios/Runner/AppDelegate.swift (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png (100%) rename {app/src => src/app}/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md (100%) rename {app/src => src/app}/ios/Runner/Base.lproj/LaunchScreen.storyboard (100%) rename {app/src => src/app}/ios/Runner/Base.lproj/Main.storyboard (100%) rename {app/src => src/app}/ios/Runner/Info.plist (100%) rename {app/src => src/app}/ios/Runner/Runner-Bridging-Header.h (100%) rename {app/src => src/app}/ios/RunnerTests/RunnerTests.swift (100%) rename {app/src => src/app}/lib/main.dart (100%) rename {app/src => src/app}/linux/.gitignore (100%) rename {app/src => src/app}/linux/CMakeLists.txt (98%) rename {app/src => src/app}/linux/flutter/CMakeLists.txt (100%) rename {app/src => src/app}/linux/flutter/generated_plugin_registrant.cc (100%) rename {app/src => src/app}/linux/flutter/generated_plugin_registrant.h (100%) rename {app/src => src/app}/linux/flutter/generated_plugins.cmake (100%) rename {app/src => src/app}/linux/runner/CMakeLists.txt (100%) rename {app/src => src/app}/linux/runner/main.cc (100%) rename {app/src => src/app}/linux/runner/my_application.cc (100%) rename {app/src => src/app}/linux/runner/my_application.h (100%) rename {app/src => src/app}/macos/.gitignore (100%) rename {app/src => src/app}/macos/Flutter/Flutter-Debug.xcconfig (100%) rename {app/src => src/app}/macos/Flutter/Flutter-Release.xcconfig (100%) rename {app/src => src/app}/macos/Flutter/GeneratedPluginRegistrant.swift (100%) rename {app/src => src/app}/macos/Podfile (100%) rename {app/src => src/app}/macos/Podfile.lock (100%) rename {app/src => src/app}/macos/Runner.xcodeproj/project.pbxproj (100%) rename {app/src => src/app}/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {app/src => src/app}/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename {app/src => src/app}/macos/Runner.xcworkspace/contents.xcworkspacedata (100%) rename {app/src => src/app}/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {app/src => src/app}/macos/Runner/AppDelegate.swift (100%) rename {app/src => src/app}/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {app/src => src/app}/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png (100%) rename {app/src => src/app}/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png (100%) rename {app/src => src/app}/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png (100%) rename {app/src => src/app}/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png (100%) rename {app/src => src/app}/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png (100%) rename {app/src => src/app}/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png (100%) rename {app/src => src/app}/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png (100%) rename {app/src => src/app}/macos/Runner/Base.lproj/MainMenu.xib (100%) rename {app/src => src/app}/macos/Runner/Configs/AppInfo.xcconfig (100%) rename {app/src => src/app}/macos/Runner/Configs/Debug.xcconfig (100%) rename {app/src => src/app}/macos/Runner/Configs/Release.xcconfig (100%) rename {app/src => src/app}/macos/Runner/Configs/Warnings.xcconfig (100%) rename {app/src => src/app}/macos/Runner/DebugProfile.entitlements (100%) rename {app/src => src/app}/macos/Runner/Info.plist (100%) rename {app/src => src/app}/macos/Runner/MainFlutterWindow.swift (100%) rename {app/src => src/app}/macos/Runner/Release.entitlements (100%) rename {app/src => src/app}/macos/RunnerTests/RunnerTests.swift (100%) rename {app/src => src/app}/pubspec.lock (100%) rename {app/src => src/app}/pubspec.yaml (100%) create mode 160000 src/app/scaffold rename {app/src => src/app}/test/widget_test.dart (100%) rename {app/src => src/app}/windows/.gitignore (100%) rename {app/src => src/app}/windows/CMakeLists.txt (98%) rename {app/src => src/app}/windows/flutter/CMakeLists.txt (100%) rename {app/src => src/app}/windows/flutter/generated_plugin_registrant.cc (100%) rename {app/src => src/app}/windows/flutter/generated_plugin_registrant.h (100%) rename {app/src => src/app}/windows/flutter/generated_plugins.cmake (100%) rename {app/src => src/app}/windows/runner/CMakeLists.txt (100%) rename {app/src => src/app}/windows/runner/Runner.rc (100%) rename {app/src => src/app}/windows/runner/flutter_window.cpp (100%) rename {app/src => src/app}/windows/runner/flutter_window.h (100%) rename {app/src => src/app}/windows/runner/main.cpp (100%) rename {app/src => src/app}/windows/runner/resource.h (100%) rename {app/src => src/app}/windows/runner/resources/app_icon.ico (100%) rename {app/src => src/app}/windows/runner/runner.exe.manifest (100%) rename {app/src => src/app}/windows/runner/utils.cpp (100%) rename {app/src => src/app}/windows/runner/utils.h (100%) rename {app/src => src/app}/windows/runner/win32_window.cpp (100%) rename {app/src => src/app}/windows/runner/win32_window.h (100%) diff --git a/.gitmodules b/.gitmodules index a6eace9..e16f7cd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,6 +7,6 @@ [submodule "build"] path = build url = ../cmaketemplate.git -[submodule "app/src/scaffold"] - path = app/src/scaffold - url = git@github.com:GeniusVentures/openapi-client-scaffold.git +[submodule "/src/app/scaffold"] + path = src/app/scaffold + url = ../openapi-client-scaffold.git diff --git a/.idea/vcs.xml b/.idea/vcs.xml index d1669ba..032b63a 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -7,6 +7,7 @@ + \ No newline at end of file diff --git a/.planning/active-workstream b/.planning/active-workstream index 65e85b4..b80f0bd 100644 --- a/.planning/active-workstream +++ b/.planning/active-workstream @@ -1 +1 @@ -neo +app diff --git a/app/src/scaffold b/app/src/scaffold deleted file mode 160000 index 72f88e0..0000000 --- a/app/src/scaffold +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 72f88e0430e31b4c6a4e47b7fd6ad977b86f47ea diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6f19baa..0a716fa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,7 +2,7 @@ # Built as part of the parent GeniusCogntiveSystem build (root CMakeLists add_subdirectory(src)). add_library(gcs_core - gcs_core.cpp + lib/gcs_core.cpp ) target_include_directories(gcs_core PUBLIC diff --git a/app/src/.gitignore b/src/app/.gitignore similarity index 100% rename from app/src/.gitignore rename to src/app/.gitignore diff --git a/app/src/.metadata b/src/app/.metadata similarity index 100% rename from app/src/.metadata rename to src/app/.metadata diff --git a/app/src/README.md b/src/app/README.md similarity index 100% rename from app/src/README.md rename to src/app/README.md diff --git a/app/src/analysis_options.yaml b/src/app/analysis_options.yaml similarity index 100% rename from app/src/analysis_options.yaml rename to src/app/analysis_options.yaml diff --git a/app/src/android/.gitignore b/src/app/android/.gitignore similarity index 100% rename from app/src/android/.gitignore rename to src/app/android/.gitignore diff --git a/app/src/android/app/build.gradle.kts b/src/app/android/app/build.gradle.kts similarity index 100% rename from app/src/android/app/build.gradle.kts rename to src/app/android/app/build.gradle.kts diff --git a/app/src/android/app/src/debug/AndroidManifest.xml b/src/app/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from app/src/android/app/src/debug/AndroidManifest.xml rename to src/app/android/app/src/debug/AndroidManifest.xml diff --git a/app/src/android/app/src/main/AndroidManifest.xml b/src/app/android/app/src/main/AndroidManifest.xml similarity index 100% rename from app/src/android/app/src/main/AndroidManifest.xml rename to src/app/android/app/src/main/AndroidManifest.xml diff --git a/app/src/android/app/src/main/kotlin/ai/gnus/flutter/flutter_app/MainActivity.kt b/src/app/android/app/src/main/kotlin/ai/gnus/flutter/flutter_app/MainActivity.kt similarity index 100% rename from app/src/android/app/src/main/kotlin/ai/gnus/flutter/flutter_app/MainActivity.kt rename to src/app/android/app/src/main/kotlin/ai/gnus/flutter/flutter_app/MainActivity.kt diff --git a/app/src/android/app/src/main/res/drawable-v21/launch_background.xml b/src/app/android/app/src/main/res/drawable-v21/launch_background.xml similarity index 100% rename from app/src/android/app/src/main/res/drawable-v21/launch_background.xml rename to src/app/android/app/src/main/res/drawable-v21/launch_background.xml diff --git a/app/src/android/app/src/main/res/drawable/launch_background.xml b/src/app/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from app/src/android/app/src/main/res/drawable/launch_background.xml rename to src/app/android/app/src/main/res/drawable/launch_background.xml diff --git a/app/src/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/src/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from app/src/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to src/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/app/src/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/src/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from app/src/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to src/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/app/src/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/src/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from app/src/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to src/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/app/src/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/src/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from app/src/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to src/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/app/src/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/src/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from app/src/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to src/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/app/src/android/app/src/main/res/values-night/styles.xml b/src/app/android/app/src/main/res/values-night/styles.xml similarity index 100% rename from app/src/android/app/src/main/res/values-night/styles.xml rename to src/app/android/app/src/main/res/values-night/styles.xml diff --git a/app/src/android/app/src/main/res/values/styles.xml b/src/app/android/app/src/main/res/values/styles.xml similarity index 100% rename from app/src/android/app/src/main/res/values/styles.xml rename to src/app/android/app/src/main/res/values/styles.xml diff --git a/app/src/android/app/src/profile/AndroidManifest.xml b/src/app/android/app/src/profile/AndroidManifest.xml similarity index 100% rename from app/src/android/app/src/profile/AndroidManifest.xml rename to src/app/android/app/src/profile/AndroidManifest.xml diff --git a/app/src/android/build.gradle.kts b/src/app/android/build.gradle.kts similarity index 100% rename from app/src/android/build.gradle.kts rename to src/app/android/build.gradle.kts diff --git a/app/src/android/gradle.properties b/src/app/android/gradle.properties similarity index 100% rename from app/src/android/gradle.properties rename to src/app/android/gradle.properties diff --git a/app/src/android/gradle/wrapper/gradle-wrapper.properties b/src/app/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from app/src/android/gradle/wrapper/gradle-wrapper.properties rename to src/app/android/gradle/wrapper/gradle-wrapper.properties diff --git a/app/src/android/settings.gradle.kts b/src/app/android/settings.gradle.kts similarity index 100% rename from app/src/android/settings.gradle.kts rename to src/app/android/settings.gradle.kts diff --git a/app/src/ios/.gitignore b/src/app/ios/.gitignore similarity index 100% rename from app/src/ios/.gitignore rename to src/app/ios/.gitignore diff --git a/app/src/ios/Flutter/AppFrameworkInfo.plist b/src/app/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from app/src/ios/Flutter/AppFrameworkInfo.plist rename to src/app/ios/Flutter/AppFrameworkInfo.plist diff --git a/app/src/ios/Flutter/Debug.xcconfig b/src/app/ios/Flutter/Debug.xcconfig similarity index 100% rename from app/src/ios/Flutter/Debug.xcconfig rename to src/app/ios/Flutter/Debug.xcconfig diff --git a/app/src/ios/Flutter/Release.xcconfig b/src/app/ios/Flutter/Release.xcconfig similarity index 100% rename from app/src/ios/Flutter/Release.xcconfig rename to src/app/ios/Flutter/Release.xcconfig diff --git a/app/src/ios/Podfile b/src/app/ios/Podfile similarity index 100% rename from app/src/ios/Podfile rename to src/app/ios/Podfile diff --git a/app/src/ios/Runner.xcodeproj/project.pbxproj b/src/app/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from app/src/ios/Runner.xcodeproj/project.pbxproj rename to src/app/ios/Runner.xcodeproj/project.pbxproj diff --git a/app/src/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/src/app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from app/src/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to src/app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/app/src/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/src/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from app/src/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to src/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/app/src/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/src/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from app/src/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to src/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/app/src/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/src/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from app/src/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to src/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/app/src/ios/Runner.xcworkspace/contents.xcworkspacedata b/src/app/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from app/src/ios/Runner.xcworkspace/contents.xcworkspacedata rename to src/app/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/app/src/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/src/app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from app/src/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to src/app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/app/src/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/src/app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from app/src/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to src/app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/app/src/ios/Runner/AppDelegate.swift b/src/app/ios/Runner/AppDelegate.swift similarity index 100% rename from app/src/ios/Runner/AppDelegate.swift rename to src/app/ios/Runner/AppDelegate.swift diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to src/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/src/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to src/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/src/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to src/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/src/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to src/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/src/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to src/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/src/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from app/src/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to src/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/app/src/ios/Runner/Base.lproj/LaunchScreen.storyboard b/src/app/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from app/src/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to src/app/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/app/src/ios/Runner/Base.lproj/Main.storyboard b/src/app/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from app/src/ios/Runner/Base.lproj/Main.storyboard rename to src/app/ios/Runner/Base.lproj/Main.storyboard diff --git a/app/src/ios/Runner/Info.plist b/src/app/ios/Runner/Info.plist similarity index 100% rename from app/src/ios/Runner/Info.plist rename to src/app/ios/Runner/Info.plist diff --git a/app/src/ios/Runner/Runner-Bridging-Header.h b/src/app/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from app/src/ios/Runner/Runner-Bridging-Header.h rename to src/app/ios/Runner/Runner-Bridging-Header.h diff --git a/app/src/ios/RunnerTests/RunnerTests.swift b/src/app/ios/RunnerTests/RunnerTests.swift similarity index 100% rename from app/src/ios/RunnerTests/RunnerTests.swift rename to src/app/ios/RunnerTests/RunnerTests.swift diff --git a/app/src/lib/main.dart b/src/app/lib/main.dart similarity index 100% rename from app/src/lib/main.dart rename to src/app/lib/main.dart diff --git a/app/src/linux/.gitignore b/src/app/linux/.gitignore similarity index 100% rename from app/src/linux/.gitignore rename to src/app/linux/.gitignore diff --git a/app/src/linux/CMakeLists.txt b/src/app/linux/CMakeLists.txt similarity index 98% rename from app/src/linux/CMakeLists.txt rename to src/app/linux/CMakeLists.txt index 298fc70..eae62a3 100644 --- a/app/src/linux/CMakeLists.txt +++ b/src/app/linux/CMakeLists.txt @@ -47,7 +47,7 @@ function(APPLY_STANDARD_SETTINGS TARGET) endfunction() # Flutter library and tool build rules. -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +set(FLUTTER_MANAGED_DIR "flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) # System-level dependencies. diff --git a/app/src/linux/flutter/CMakeLists.txt b/src/app/linux/flutter/CMakeLists.txt similarity index 100% rename from app/src/linux/flutter/CMakeLists.txt rename to src/app/linux/flutter/CMakeLists.txt diff --git a/app/src/linux/flutter/generated_plugin_registrant.cc b/src/app/linux/flutter/generated_plugin_registrant.cc similarity index 100% rename from app/src/linux/flutter/generated_plugin_registrant.cc rename to src/app/linux/flutter/generated_plugin_registrant.cc diff --git a/app/src/linux/flutter/generated_plugin_registrant.h b/src/app/linux/flutter/generated_plugin_registrant.h similarity index 100% rename from app/src/linux/flutter/generated_plugin_registrant.h rename to src/app/linux/flutter/generated_plugin_registrant.h diff --git a/app/src/linux/flutter/generated_plugins.cmake b/src/app/linux/flutter/generated_plugins.cmake similarity index 100% rename from app/src/linux/flutter/generated_plugins.cmake rename to src/app/linux/flutter/generated_plugins.cmake diff --git a/app/src/linux/runner/CMakeLists.txt b/src/app/linux/runner/CMakeLists.txt similarity index 100% rename from app/src/linux/runner/CMakeLists.txt rename to src/app/linux/runner/CMakeLists.txt diff --git a/app/src/linux/runner/main.cc b/src/app/linux/runner/main.cc similarity index 100% rename from app/src/linux/runner/main.cc rename to src/app/linux/runner/main.cc diff --git a/app/src/linux/runner/my_application.cc b/src/app/linux/runner/my_application.cc similarity index 100% rename from app/src/linux/runner/my_application.cc rename to src/app/linux/runner/my_application.cc diff --git a/app/src/linux/runner/my_application.h b/src/app/linux/runner/my_application.h similarity index 100% rename from app/src/linux/runner/my_application.h rename to src/app/linux/runner/my_application.h diff --git a/app/src/macos/.gitignore b/src/app/macos/.gitignore similarity index 100% rename from app/src/macos/.gitignore rename to src/app/macos/.gitignore diff --git a/app/src/macos/Flutter/Flutter-Debug.xcconfig b/src/app/macos/Flutter/Flutter-Debug.xcconfig similarity index 100% rename from app/src/macos/Flutter/Flutter-Debug.xcconfig rename to src/app/macos/Flutter/Flutter-Debug.xcconfig diff --git a/app/src/macos/Flutter/Flutter-Release.xcconfig b/src/app/macos/Flutter/Flutter-Release.xcconfig similarity index 100% rename from app/src/macos/Flutter/Flutter-Release.xcconfig rename to src/app/macos/Flutter/Flutter-Release.xcconfig diff --git a/app/src/macos/Flutter/GeneratedPluginRegistrant.swift b/src/app/macos/Flutter/GeneratedPluginRegistrant.swift similarity index 100% rename from app/src/macos/Flutter/GeneratedPluginRegistrant.swift rename to src/app/macos/Flutter/GeneratedPluginRegistrant.swift diff --git a/app/src/macos/Podfile b/src/app/macos/Podfile similarity index 100% rename from app/src/macos/Podfile rename to src/app/macos/Podfile diff --git a/app/src/macos/Podfile.lock b/src/app/macos/Podfile.lock similarity index 100% rename from app/src/macos/Podfile.lock rename to src/app/macos/Podfile.lock diff --git a/app/src/macos/Runner.xcodeproj/project.pbxproj b/src/app/macos/Runner.xcodeproj/project.pbxproj similarity index 100% rename from app/src/macos/Runner.xcodeproj/project.pbxproj rename to src/app/macos/Runner.xcodeproj/project.pbxproj diff --git a/app/src/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/src/app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from app/src/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to src/app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/app/src/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/src/app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from app/src/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to src/app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/app/src/macos/Runner.xcworkspace/contents.xcworkspacedata b/src/app/macos/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from app/src/macos/Runner.xcworkspace/contents.xcworkspacedata rename to src/app/macos/Runner.xcworkspace/contents.xcworkspacedata diff --git a/app/src/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/src/app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from app/src/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to src/app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/app/src/macos/Runner/AppDelegate.swift b/src/app/macos/Runner/AppDelegate.swift similarity index 100% rename from app/src/macos/Runner/AppDelegate.swift rename to src/app/macos/Runner/AppDelegate.swift diff --git a/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/src/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to src/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/src/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png similarity index 100% rename from app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png rename to src/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png diff --git a/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/src/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png similarity index 100% rename from app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png rename to src/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png diff --git a/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/src/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png similarity index 100% rename from app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png rename to src/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png diff --git a/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/src/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png similarity index 100% rename from app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png rename to src/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png diff --git a/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/src/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png similarity index 100% rename from app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png rename to src/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png diff --git a/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/src/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png similarity index 100% rename from app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png rename to src/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png diff --git a/app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/src/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png similarity index 100% rename from app/src/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png rename to src/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png diff --git a/app/src/macos/Runner/Base.lproj/MainMenu.xib b/src/app/macos/Runner/Base.lproj/MainMenu.xib similarity index 100% rename from app/src/macos/Runner/Base.lproj/MainMenu.xib rename to src/app/macos/Runner/Base.lproj/MainMenu.xib diff --git a/app/src/macos/Runner/Configs/AppInfo.xcconfig b/src/app/macos/Runner/Configs/AppInfo.xcconfig similarity index 100% rename from app/src/macos/Runner/Configs/AppInfo.xcconfig rename to src/app/macos/Runner/Configs/AppInfo.xcconfig diff --git a/app/src/macos/Runner/Configs/Debug.xcconfig b/src/app/macos/Runner/Configs/Debug.xcconfig similarity index 100% rename from app/src/macos/Runner/Configs/Debug.xcconfig rename to src/app/macos/Runner/Configs/Debug.xcconfig diff --git a/app/src/macos/Runner/Configs/Release.xcconfig b/src/app/macos/Runner/Configs/Release.xcconfig similarity index 100% rename from app/src/macos/Runner/Configs/Release.xcconfig rename to src/app/macos/Runner/Configs/Release.xcconfig diff --git a/app/src/macos/Runner/Configs/Warnings.xcconfig b/src/app/macos/Runner/Configs/Warnings.xcconfig similarity index 100% rename from app/src/macos/Runner/Configs/Warnings.xcconfig rename to src/app/macos/Runner/Configs/Warnings.xcconfig diff --git a/app/src/macos/Runner/DebugProfile.entitlements b/src/app/macos/Runner/DebugProfile.entitlements similarity index 100% rename from app/src/macos/Runner/DebugProfile.entitlements rename to src/app/macos/Runner/DebugProfile.entitlements diff --git a/app/src/macos/Runner/Info.plist b/src/app/macos/Runner/Info.plist similarity index 100% rename from app/src/macos/Runner/Info.plist rename to src/app/macos/Runner/Info.plist diff --git a/app/src/macos/Runner/MainFlutterWindow.swift b/src/app/macos/Runner/MainFlutterWindow.swift similarity index 100% rename from app/src/macos/Runner/MainFlutterWindow.swift rename to src/app/macos/Runner/MainFlutterWindow.swift diff --git a/app/src/macos/Runner/Release.entitlements b/src/app/macos/Runner/Release.entitlements similarity index 100% rename from app/src/macos/Runner/Release.entitlements rename to src/app/macos/Runner/Release.entitlements diff --git a/app/src/macos/RunnerTests/RunnerTests.swift b/src/app/macos/RunnerTests/RunnerTests.swift similarity index 100% rename from app/src/macos/RunnerTests/RunnerTests.swift rename to src/app/macos/RunnerTests/RunnerTests.swift diff --git a/app/src/pubspec.lock b/src/app/pubspec.lock similarity index 100% rename from app/src/pubspec.lock rename to src/app/pubspec.lock diff --git a/app/src/pubspec.yaml b/src/app/pubspec.yaml similarity index 100% rename from app/src/pubspec.yaml rename to src/app/pubspec.yaml diff --git a/src/app/scaffold b/src/app/scaffold new file mode 160000 index 0000000..1cd3759 --- /dev/null +++ b/src/app/scaffold @@ -0,0 +1 @@ +Subproject commit 1cd3759699177bc6cc7f590bf9bec81d841b05e5 diff --git a/app/src/test/widget_test.dart b/src/app/test/widget_test.dart similarity index 100% rename from app/src/test/widget_test.dart rename to src/app/test/widget_test.dart diff --git a/app/src/windows/.gitignore b/src/app/windows/.gitignore similarity index 100% rename from app/src/windows/.gitignore rename to src/app/windows/.gitignore diff --git a/app/src/windows/CMakeLists.txt b/src/app/windows/CMakeLists.txt similarity index 98% rename from app/src/windows/CMakeLists.txt rename to src/app/windows/CMakeLists.txt index 8b59335..9dcc820 100644 --- a/app/src/windows/CMakeLists.txt +++ b/src/app/windows/CMakeLists.txt @@ -46,7 +46,7 @@ function(APPLY_STANDARD_SETTINGS TARGET) endfunction() # Flutter library and tool build rules. -set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +set(FLUTTER_MANAGED_DIR "flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) # Application build; see runner/CMakeLists.txt. diff --git a/app/src/windows/flutter/CMakeLists.txt b/src/app/windows/flutter/CMakeLists.txt similarity index 100% rename from app/src/windows/flutter/CMakeLists.txt rename to src/app/windows/flutter/CMakeLists.txt diff --git a/app/src/windows/flutter/generated_plugin_registrant.cc b/src/app/windows/flutter/generated_plugin_registrant.cc similarity index 100% rename from app/src/windows/flutter/generated_plugin_registrant.cc rename to src/app/windows/flutter/generated_plugin_registrant.cc diff --git a/app/src/windows/flutter/generated_plugin_registrant.h b/src/app/windows/flutter/generated_plugin_registrant.h similarity index 100% rename from app/src/windows/flutter/generated_plugin_registrant.h rename to src/app/windows/flutter/generated_plugin_registrant.h diff --git a/app/src/windows/flutter/generated_plugins.cmake b/src/app/windows/flutter/generated_plugins.cmake similarity index 100% rename from app/src/windows/flutter/generated_plugins.cmake rename to src/app/windows/flutter/generated_plugins.cmake diff --git a/app/src/windows/runner/CMakeLists.txt b/src/app/windows/runner/CMakeLists.txt similarity index 100% rename from app/src/windows/runner/CMakeLists.txt rename to src/app/windows/runner/CMakeLists.txt diff --git a/app/src/windows/runner/Runner.rc b/src/app/windows/runner/Runner.rc similarity index 100% rename from app/src/windows/runner/Runner.rc rename to src/app/windows/runner/Runner.rc diff --git a/app/src/windows/runner/flutter_window.cpp b/src/app/windows/runner/flutter_window.cpp similarity index 100% rename from app/src/windows/runner/flutter_window.cpp rename to src/app/windows/runner/flutter_window.cpp diff --git a/app/src/windows/runner/flutter_window.h b/src/app/windows/runner/flutter_window.h similarity index 100% rename from app/src/windows/runner/flutter_window.h rename to src/app/windows/runner/flutter_window.h diff --git a/app/src/windows/runner/main.cpp b/src/app/windows/runner/main.cpp similarity index 100% rename from app/src/windows/runner/main.cpp rename to src/app/windows/runner/main.cpp diff --git a/app/src/windows/runner/resource.h b/src/app/windows/runner/resource.h similarity index 100% rename from app/src/windows/runner/resource.h rename to src/app/windows/runner/resource.h diff --git a/app/src/windows/runner/resources/app_icon.ico b/src/app/windows/runner/resources/app_icon.ico similarity index 100% rename from app/src/windows/runner/resources/app_icon.ico rename to src/app/windows/runner/resources/app_icon.ico diff --git a/app/src/windows/runner/runner.exe.manifest b/src/app/windows/runner/runner.exe.manifest similarity index 100% rename from app/src/windows/runner/runner.exe.manifest rename to src/app/windows/runner/runner.exe.manifest diff --git a/app/src/windows/runner/utils.cpp b/src/app/windows/runner/utils.cpp similarity index 100% rename from app/src/windows/runner/utils.cpp rename to src/app/windows/runner/utils.cpp diff --git a/app/src/windows/runner/utils.h b/src/app/windows/runner/utils.h similarity index 100% rename from app/src/windows/runner/utils.h rename to src/app/windows/runner/utils.h diff --git a/app/src/windows/runner/win32_window.cpp b/src/app/windows/runner/win32_window.cpp similarity index 100% rename from app/src/windows/runner/win32_window.cpp rename to src/app/windows/runner/win32_window.cpp diff --git a/app/src/windows/runner/win32_window.h b/src/app/windows/runner/win32_window.h similarity index 100% rename from app/src/windows/runner/win32_window.h rename to src/app/windows/runner/win32_window.h From c00aa3612185d113640efdcd5f5b985185977eb8 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Wed, 19 Aug 2026 03:20:26 -0400 Subject: [PATCH 17/49] Compilation fixes --- GNUS-NEO-SWARM | 2 +- cmake/CommonBuildParameters.cmake | 361 +++++++++++++++++++++++++++++- 2 files changed, 361 insertions(+), 2 deletions(-) diff --git a/GNUS-NEO-SWARM b/GNUS-NEO-SWARM index b388aab..9c41c18 160000 --- a/GNUS-NEO-SWARM +++ b/GNUS-NEO-SWARM @@ -1 +1 @@ -Subproject commit b388aabea9568dfd4e6d6acbbc7a40f32f532058 +Subproject commit 9c41c18489d02d12ccd9fd1968c87f36bb0f80f4 diff --git a/cmake/CommonBuildParameters.cmake b/cmake/CommonBuildParameters.cmake index 28dc1e0..519f1ba 100644 --- a/cmake/CommonBuildParameters.cmake +++ b/cmake/CommonBuildParameters.cmake @@ -86,6 +86,25 @@ set(_BOOST_ROOT "${THIRDPARTY_BUILD_DIR}/boost/build") set(Boost_LIB_DIR "${_BOOST_ROOT}/lib") set(Boost_INCLUDE_DIR "${_BOOST_ROOT}/include/boost-${BOOST_VERSION_2U}") set(Boost_DIR "${Boost_LIB_DIR}/cmake/Boost-${BOOST_VERSION}") +# Per-component DIR hints (needed by GeniusSDK's own component list below — +# ported from GeniusNetwork/GeniusSDK/cmake/CommonBuildParameters.cmake). +set(boost_atomic_DIR "${Boost_LIB_DIR}/cmake/boost_atomic-${BOOST_VERSION}") +set(boost_chrono_DIR "${Boost_LIB_DIR}/cmake/boost_chrono-${BOOST_VERSION}") +set(boost_container_DIR "${Boost_LIB_DIR}/cmake/boost_container-${BOOST_VERSION}") +set(boost_context_DIR "${Boost_LIB_DIR}/cmake/boost_context-${BOOST_VERSION}") +set(boost_date_time_DIR "${Boost_LIB_DIR}/cmake/boost_date_time-${BOOST_VERSION}") +set(boost_filesystem_DIR "${Boost_LIB_DIR}/cmake/boost_filesystem-${BOOST_VERSION}") +set(boost_headers_DIR "${Boost_LIB_DIR}/cmake/boost_headers-${BOOST_VERSION}") +set(boost_json_DIR "${Boost_LIB_DIR}/cmake/boost_json-${BOOST_VERSION}") +set(boost_log_DIR "${Boost_LIB_DIR}/cmake/boost_log-${BOOST_VERSION}") +set(boost_log_setup_DIR "${Boost_LIB_DIR}/cmake/boost_log_setup-${BOOST_VERSION}") +set(boost_program_options_DIR "${Boost_LIB_DIR}/cmake/boost_program_options-${BOOST_VERSION}") +set(boost_random_DIR "${Boost_LIB_DIR}/cmake/boost_random-${BOOST_VERSION}") +set(boost_regex_DIR "${Boost_LIB_DIR}/cmake/boost_regex-${BOOST_VERSION}") +set(boost_system_DIR "${Boost_LIB_DIR}/cmake/boost_system-${BOOST_VERSION}") +set(boost_thread_DIR "${Boost_LIB_DIR}/cmake/boost_thread-${BOOST_VERSION}") +set(boost_coroutine_DIR "${Boost_LIB_DIR}/cmake/boost_coroutine-${BOOST_VERSION}") +set(boost_unit_test_framework_DIR "${Boost_LIB_DIR}/cmake/boost_unit_test_framework-${BOOST_VERSION}") set(Boost_USE_MULTITHREADED ON) set(Boost_USE_STATIC_LIBS ON) set(Boost_NO_SYSTEM_PATHS ON) @@ -95,7 +114,17 @@ if(POLICY CMP0167) cmake_policy(SET CMP0167 OLD) endif() -find_package(Boost REQUIRED COMPONENTS date_time filesystem random regex system thread log log_setup program_options json) +option(SGNS_STACKTRACE_BACKTRACE "Use BOOST_STACKTRACE_USE_BACKTRACE in stacktraces, for POSIX" OFF) +if(SGNS_STACKTRACE_BACKTRACE) + add_definitions(-DSGNS_STACKTRACE_BACKTRACE=1) + if(BACKTRACE_INCLUDE) + add_definitions(-DBOOST_STACKTRACE_BACKTRACE_INCLUDE_FILE=${BACKTRACE_INCLUDE}) + endif() +endif() + +# Component list is the union of GCS's own needs and GeniusSDK's genius_node +# needs (container, unit_test_framework, coroutine added per GeniusSDK's file). +find_package(Boost REQUIRED COMPONENTS container date_time filesystem random regex system thread log log_setup program_options json unit_test_framework coroutine) include_directories(${Boost_INCLUDE_DIRS}) # zlib @@ -120,6 +149,336 @@ find_package(libsecp256k1 CONFIG REQUIRED) set(nlohmann_json_DIR "${THIRDPARTY_BUILD_DIR}/json/share/cmake/nlohmann_json") find_package(nlohmann_json CONFIG REQUIRED) +# -------------------------------------------------------- +# Remaining GeniusSDK transitive dependencies. GeniusCognitiveSystem now +# links GeniusSDK directly (sgns::GeniusSDK -> sgns::genius_node), and +# genius_node is GeniusSDK's own internal aggregate library — its exported +# config does not re-chain further find_package() calls for its own PUBLIC +# dependencies, so this file (the consumer) must resolve every one of them +# itself, exactly as GeniusNetwork/GeniusSDK/cmake/CommonBuildParameters.cmake +# does for its own build. Ported verbatim from that file; only entries GCS +# already had (GTest, protobuf, OpenSSL, Microsoft.GSL, zlib, fmt, spdlog, +# libsecp256k1, nlohmann_json) are skipped here to avoid duplication. + +# MNN +set(MNN_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/MNN/include") +set(MNN_DIR "${THIRDPARTY_BUILD_DIR}/MNN/lib/cmake/MNN") +find_package(MNN CONFIG REQUIRED) +include_directories(${MNN_INCLUDE_DIR}) + +# vk-bootstrap +set(vk-bootstrap_DIR "${THIRDPARTY_BUILD_DIR}/vk-bootstrap/lib/cmake/vk-bootstrap") +find_package(vk-bootstrap CONFIG REQUIRED) + +# soralog +set(soralog_DIR "${THIRDPARTY_BUILD_DIR}/soralog/lib/cmake/soralog") +set(soralog_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/soralog/include") +find_package(soralog CONFIG REQUIRED) + +# yaml-cpp +set(yaml-cpp_DIR "${THIRDPARTY_BUILD_DIR}/yaml-cpp/lib/cmake/yaml-cpp") +set(yaml-cpp_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/yaml-cpp/include") +find_package(yaml-cpp CONFIG REQUIRED) + +# snappy +set(Snappy_DIR "${THIRDPARTY_BUILD_DIR}/snappy/lib/cmake/Snappy") +find_package(Snappy CONFIG REQUIRED) + +# rocksdb +set(RocksDB_DIR "${THIRDPARTY_BUILD_DIR}/rocksdb/lib/cmake/rocksdb") +set(RocksDB_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/rocksdb/include") +find_package(RocksDB CONFIG REQUIRED) + +# stb +include_directories(${THIRDPARTY_BUILD_DIR}/stb/include) + +# tsl_hat_trie +set(tsl_hat_trie_DIR "${THIRDPARTY_BUILD_DIR}/tsl_hat_trie/lib/cmake/tsl_hat_trie") +set(tsl_hat_trie_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/tsl_hat_trie/include") +find_package(tsl_hat_trie CONFIG REQUIRED) + +# Boost.DI +set(Boost.DI_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/Boost.DI/include") +set(Boost.DI_DIR "${THIRDPARTY_BUILD_DIR}/Boost.DI/lib/cmake/Boost.DI") +find_package(Boost.DI CONFIG REQUIRED) + +# SQLiteModernCpp +set(SQLiteModernCpp_ROOT_DIR "${THIRDPARTY_BUILD_DIR}/SQLiteModernCpp") +set(SQLiteModernCpp_DIR "${SQLiteModernCpp_ROOT_DIR}/lib/cmake/SQLiteModernCpp") +set(SQLiteModernCpp_LIB_DIR "${SQLiteModernCpp_ROOT_DIR}/lib") +set(SQLiteModernCpp_INCLUDE_DIR "${SQLiteModernCpp_ROOT_DIR}/include") + +# sqlite3 +set(sqlite3_ROOT_DIR "${THIRDPARTY_BUILD_DIR}/sqlite3") +set(sqlite3_DIR "${sqlite3_ROOT_DIR}/lib/cmake/sqlite3") +set(sqlite3_LIB_DIR "${sqlite3_ROOT_DIR}/lib") +set(sqlite3_INCLUDE_DIR "${sqlite3_ROOT_DIR}/include") + +# cares +set(c-ares_DIR "${THIRDPARTY_BUILD_DIR}/cares/lib/cmake/c-ares" CACHE PATH "Path to c-ares install folder") +set(c-ares_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/cares/include" CACHE PATH "Path to c-ares include folder") + +# libp2p +set(libp2p_DIR "${THIRDPARTY_BUILD_DIR}/libp2p/lib/cmake/libp2p") +set(libp2p_LIBRARY_DIR "${THIRDPARTY_BUILD_DIR}/libp2p/lib") +set(libp2p_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/libp2p/include") +find_package(libp2p CONFIG REQUIRED) + +# Find and include cares if libp2p have not included it +if(NOT TARGET c-ares::cares_static) + find_package(c-ares CONFIG REQUIRED) +endif() + +# ipfs-lite-cpp +set(ipfs-lite-cpp_DIR "${THIRDPARTY_BUILD_DIR}/ipfs-lite-cpp/lib/cmake/ipfs-lite-cpp") +set(ipfs-lite-cpp_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/ipfs-lite-cpp/include") +set(ipfs-lite-cpp_LIB_DIR "${THIRDPARTY_BUILD_DIR}/ipfs-lite-cpp/lib") +set(CBOR_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/ipfs-lite-cpp/include/deps/tinycbor/src") +find_package(ipfs-lite-cpp CONFIG REQUIRED) + +# ipfs-pubsub +set(ipfs-pubsub_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/ipfs-pubsub/include") +set(ipfs-pubsub_DIR "${THIRDPARTY_BUILD_DIR}/ipfs-pubsub/lib/cmake/ipfs-pubsub") +find_package(ipfs-pubsub CONFIG REQUIRED) + +# ipfs-bitswap-cpp +set(ipfs-bitswap-cpp_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/ipfs-bitswap-cpp/include") +set(ipfs-bitswap-cpp_DIR "${THIRDPARTY_BUILD_DIR}/ipfs-bitswap-cpp/lib/cmake/ipfs-bitswap-cpp") +find_package(ipfs-bitswap-cpp CONFIG REQUIRED) + +# ed25519 +set(ed25519_DIR "${THIRDPARTY_BUILD_DIR}/ed25519/lib/cmake/ed25519") +set(ed25519_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/ed25519/include") +find_package(ed25519 CONFIG REQUIRED) + +# RapidJSON +set(RapidJSON_DIR "${THIRDPARTY_BUILD_DIR}/rapidjson/lib/cmake/RapidJSON") +set(RapidJSON_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/rapidjson/include") +find_package(RapidJSON CONFIG REQUIRED) +include_directories(${RapidJSON_INCLUDE_DIR}) + +# jsonrpc-lean +set(jsonrpc_lean_INCLUDE_DIR "${THIRDPARTY_DIR}/jsonrpc-lean/include") +include_directories(${jsonrpc_lean_INCLUDE_DIR}) + +# xxhash +set(xxHash_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/xxhash/include") +set(xxHash_LIBRARY_DIR "${THIRDPARTY_BUILD_DIR}/xxhash/lib") +set(xxHash_DIR "${THIRDPARTY_BUILD_DIR}/xxhash/lib/cmake/xxHash") +find_package(xxHash CONFIG REQUIRED) + +# Prefer package config files while loading Libssh2's dependencies. +# Libssh2 config calls `find_dependency(ZLIB)` without `CONFIG`, which can +# otherwise resolve to CMake's FindZLIB module on Windows CI. +set(_SGNS_CMAKE_FIND_PACKAGE_PREFER_CONFIG_WAS_DEFINED FALSE) +if(DEFINED CMAKE_FIND_PACKAGE_PREFER_CONFIG) + set(_SGNS_CMAKE_FIND_PACKAGE_PREFER_CONFIG_WAS_DEFINED TRUE) + set(_SGNS_CMAKE_FIND_PACKAGE_PREFER_CONFIG_PREV "${CMAKE_FIND_PACKAGE_PREFER_CONFIG}") +endif() +set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON) + +# libssh2 +set(Libssh2_DIR "${THIRDPARTY_BUILD_DIR}/libssh2/lib/cmake/libssh2") +find_package(Libssh2 CONFIG REQUIRED) + +if(_SGNS_CMAKE_FIND_PACKAGE_PREFER_CONFIG_WAS_DEFINED) + set(CMAKE_FIND_PACKAGE_PREFER_CONFIG "${_SGNS_CMAKE_FIND_PACKAGE_PREFER_CONFIG_PREV}") +else() + unset(CMAKE_FIND_PACKAGE_PREFER_CONFIG) +endif() +unset(_SGNS_CMAKE_FIND_PACKAGE_PREFER_CONFIG_PREV) +unset(_SGNS_CMAKE_FIND_PACKAGE_PREFER_CONFIG_WAS_DEFINED) + +# AsyncIOManager +set(AsyncIOManager_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/AsyncIOManager/include") +set(AsyncIOManager_LIBRARY_DIR "${THIRDPARTY_BUILD_DIR}/AsyncIOManager/lib") +set(AsyncIOManager_DIR "${THIRDPARTY_BUILD_DIR}/AsyncIOManager/lib/cmake/AsyncIOManager") +find_package(AsyncIOManager CONFIG REQUIRED) + +# gnus_upnp +set(gnus_upnp_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/gnus_upnp/include") +set(gnus_upnp_LIBRARY_DIR "${THIRDPARTY_BUILD_DIR}/gnus_upnp/lib") +set(gnus_upnp_DIR "${THIRDPARTY_BUILD_DIR}/gnus_upnp/lib/cmake/gnus_upnp") +find_package(gnus_upnp CONFIG REQUIRED) + +# wallet-core +set(TrustWalletCore_LIBRARY_DIR "${THIRDPARTY_BUILD_DIR}/wallet-core/lib") +set(TrustWalletCore_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/wallet-core/include") + +find_library(TrezorCrypto_PATH TrezorCrypto PATHS ${TrustWalletCore_LIBRARY_DIR} REQUIRED) +find_library(wallet_core_rs_PATH wallet_core_rs PATHS ${TrustWalletCore_LIBRARY_DIR} REQUIRED) +find_library(TrustWalletCore_PATH TrustWalletCore PATHS ${TrustWalletCore_LIBRARY_DIR} REQUIRED) + +add_library(TrezorCrypto STATIC IMPORTED) +add_library(wallet_core_rs STATIC IMPORTED) +add_library(TrustWalletCore STATIC IMPORTED) + +set_target_properties(TrezorCrypto PROPERTIES IMPORTED_LOCATION "${TrezorCrypto_PATH}") +set_target_properties(wallet_core_rs PROPERTIES IMPORTED_LOCATION "${wallet_core_rs_PATH}") +set_target_properties(TrustWalletCore PROPERTIES IMPORTED_LOCATION "${TrustWalletCore_PATH}") + +target_include_directories(TrustWalletCore INTERFACE "${TrustWalletCore_INCLUDE_DIR}") + +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_package(PkgConfig) + pkg_check_modules(LIBSECRET REQUIRED IMPORTED_TARGET libsecret-1>=0.18.4) +endif() + +# -------------------------------------------------------- +# zkLLVM / crypto3 (GCS's own CommonCompilerOptions.cmake already resolves +# ZKLLVM_BUILD_DIR — this only adds the IMPORTED targets GeniusSDK's own +# crypto3-consuming code links against, plus LLVM itself). +add_library(crypto3::algebra INTERFACE IMPORTED) +add_library(crypto3::block INTERFACE IMPORTED) +add_library(crypto3::blueprint INTERFACE IMPORTED) +add_library(crypto3::codec INTERFACE IMPORTED) +add_library(crypto3::math INTERFACE IMPORTED) +add_library(crypto3::multiprecision INTERFACE IMPORTED) +add_library(crypto3::pkpad INTERFACE IMPORTED) +add_library(crypto3::pubkey INTERFACE IMPORTED) +add_library(crypto3::random INTERFACE IMPORTED) +add_library(crypto3::zk INTERFACE IMPORTED) +add_library(marshalling::core INTERFACE IMPORTED) +add_library(marshalling::crypto3_algebra INTERFACE IMPORTED) +add_library(marshalling::crypto3_multiprecision INTERFACE IMPORTED) +add_library(marshalling::crypto3_zk INTERFACE IMPORTED) + +foreach(_crypto3_tgt crypto3::algebra crypto3::block crypto3::blueprint crypto3::codec + crypto3::math crypto3::multiprecision crypto3::pkpad crypto3::pubkey + crypto3::random crypto3::zk marshalling::core marshalling::crypto3_algebra + marshalling::crypto3_multiprecision marshalling::crypto3_zk) + set_target_properties(${_crypto3_tgt} PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${ZKLLVM_BUILD_DIR}/zkLLVM/include" + ) +endforeach() + +set(zkLLVM_INCLUDE_DIR "${ZKLLVM_BUILD_DIR}/zkLLVM/include") + +# llvm +set(LLVM_DIR "${ZKLLVM_BUILD_DIR}/zkLLVM/lib/cmake/llvm") +find_package(LLVM CONFIG REQUIRED) + +# -------------------------------------------------------- +# Vulkan (GPU acceleration — needed by MNN/SGProcessingManager via +# GNUS-NEO-SWARM). Established here, before either add_subdirectory() call +# below, so the resulting Vulkan::Vulkan target is visible to both the +# GNUS-NEO-SWARM subtree and the GCS-level src/ subtree (sibling +# add_subdirectory scopes don't share targets with each other, only with +# their common parent). Ported from +# GeniusNetwork/GeniusSDK/cmake/CommonBuildParameters.cmake. +if(APPLE) + if(IOS) + # Settings specifically for iOS + set(Vulkan_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/moltenvk/build/include") + set(Vulkan_LIBRARY "${THIRDPARTY_BUILD_DIR}/moltenvk/build/lib/MoltenVK.xcframework") + else() + # Settings for macOS + set(Vulkan_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/moltenvk/build/include") + set(Vulkan_LIBRARY "${THIRDPARTY_BUILD_DIR}/moltenvk/build/lib/MoltenVK.xcframework") + endif() +endif() + +set(VulkanHeaders_DIR "${THIRDPARTY_BUILD_DIR}/Vulkan-Headers/share/cmake/VulkanHeaders" CACHE PATH "Path to Vulkan-Headers install folder") +find_package(VulkanHeaders CONFIG REQUIRED) +find_package(Vulkan) + +if(NOT TARGET Vulkan::Vulkan) + set(Vulkan_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/Vulkan-Headers/include") + if(NOT DEFINED $ENV{VULKAN_SDK}) + set(ENV{VULKAN_SDK} "${THIRDPARTY_BUILD_DIR}/Vulkan-Loader") + endif() + + find_package(Vulkan REQUIRED) +endif() + +# Force Vulkan::Vulkan to use the vendored Vulkan-Headers on every platform, +# even when find_package(Vulkan) resolves against a system-installed SDK. +# vk-bootstrap/MNN here are built against the vendored headers, so mixing in +# a different system header version causes unknown-type errors downstream. +set_target_properties(Vulkan::Vulkan PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${THIRDPARTY_BUILD_DIR}/Vulkan-Headers/include" +) + +# -------------------------------------------------------- +# SuperGenius (sibling repo under GeniusNetwork). Must be found BEFORE +# GeniusSDK below — GeniusSDK's exported sgns::GeniusSDK target links +# sgns::genius_node, which itself links SuperGenius's exported targets, so +# those packages need to already exist by the time find_package(GeniusSDK) +# resolves its own target graph. Ported verbatim from +# GeniusNetwork/GeniusSDK/cmake/CommonBuildParameters.cmake, which locates +# SuperGenius the same way for the exact same reason. +if(NOT DEFINED SUPERGENIUS_BUILD_DIR) + if(NOT DEFINED SUPERGENIUS_DIR) + if(EXISTS "${PROJECT_SUPER_ROOT}/SuperGenius") + print("Setting default SuperGenius directory") + set(SUPERGENIUS_DIR "${PROJECT_SUPER_ROOT}/SuperGenius" CACHE STRING "Default SuperGenius Library") + + # get absolute path + cmake_path(SET SUPERGENIUS_DIR NORMALIZE "${SUPERGENIUS_DIR}") + else() + message(FATAL_ERROR "Cannot find SuperGenius directory required to build") + endif() + endif() + print("Setting SuperGenius build directory default") + get_filename_component(BUILD_PLATFORM_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME) + set(SUPERGENIUS_BUILD_DIR "${SUPERGENIUS_DIR}/build/${BUILD_PLATFORM_NAME}/${CMAKE_BUILD_TYPE}${ABI_SUBFOLDER_NAME}" CACHE STRING "Default Super Genius Build Directory") +endif() + +# SuperGenius project +set(evmrelay_DIR "${SUPERGENIUS_BUILD_DIR}/SuperGenius/lib/cmake/evmrelay/") +set(SuperGenius_DIR "${SUPERGENIUS_BUILD_DIR}/SuperGenius/lib/cmake/SuperGenius/") +set(ProofSystem_DIR "${SUPERGENIUS_BUILD_DIR}/SuperGenius/lib/cmake/ProofSystem/") +set(SGProcessingManager_DIR "${SUPERGENIUS_BUILD_DIR}/SuperGenius/lib/cmake/SGProcessingManager/") + +print("SuperGenius_DIR: ${SuperGenius_DIR}") + +# shaderc installs no CMake package config, so SuperGenius hand-rolls this +# IMPORTED target rather than exporting one; SGProcessingManagerTargets.cmake's +# SGShaderCompiler link interface references shaderc::shaderc directly, so +# consumers of that export (like this file) must define the same target +# themselves before find_package(SGProcessingManager) resolves it below. +if(NOT TARGET shaderc::shaderc) + add_library(shaderc::shaderc STATIC IMPORTED GLOBAL) + set_target_properties(shaderc::shaderc PROPERTIES + IMPORTED_LOCATION "${THIRDPARTY_BUILD_DIR}/shaderc/lib/${CMAKE_STATIC_LIBRARY_PREFIX}shaderc_combined${CMAKE_STATIC_LIBRARY_SUFFIX}" + INTERFACE_INCLUDE_DIRECTORIES "${THIRDPARTY_BUILD_DIR}/shaderc/include" + ) +endif() + +find_package(evmrelay CONFIG REQUIRED) +find_package(ProofSystem CONFIG REQUIRED) +find_package(SGProcessingManager CONFIG REQUIRED) +find_package(SuperGenius CONFIG REQUIRED) +include_directories(${SuperGenius_INCLUDE_DIR}) + +# -------------------------------------------------------- +# GeniusSDK (sibling repo under GeniusNetwork — GeniusCognitiveSystem links +# directly against it). Located and found the same way GeniusSDK's own +# CommonBuildParameters.cmake locates SuperGenius: a sibling of +# PROJECT_SUPER_ROOT, with a per-platform/build-type install tree at +# /build////lib/cmake//. +if(NOT DEFINED GENIUSSDK_BUILD_DIR) + if(NOT DEFINED GENIUSSDK_DIR) + if(EXISTS "${PROJECT_SUPER_ROOT}/GeniusSDK") + print("Setting default GeniusSDK directory") + set(GENIUSSDK_DIR "${PROJECT_SUPER_ROOT}/GeniusSDK" CACHE STRING "Default GeniusSDK Library") + + # get absolute path + cmake_path(SET GENIUSSDK_DIR NORMALIZE "${GENIUSSDK_DIR}") + else() + message(FATAL_ERROR "Cannot find GeniusSDK directory required to build") + endif() + endif() + print("Setting GeniusSDK build directory default") + get_filename_component(BUILD_PLATFORM_NAME ${CMAKE_CURRENT_SOURCE_DIR} NAME) + set(GENIUSSDK_BUILD_DIR "${GENIUSSDK_DIR}/build/${BUILD_PLATFORM_NAME}/${CMAKE_BUILD_TYPE}${ABI_SUBFOLDER_NAME}" CACHE STRING "Default GeniusSDK Build Directory") +endif() + +# GeniusSDK project +set(GeniusSDK_DIR "${GENIUSSDK_BUILD_DIR}/GeniusSDK/lib/cmake/GeniusSDK/") +print("GeniusSDK_DIR: ${GeniusSDK_DIR}") +find_package(GeniusSDK CONFIG REQUIRED) + # -------------------------------------------------------- # Project options option(BUILD_TESTS "Build tests" FALSE) From d22919323d78b22439f83216ddf19e6658db0903 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Thu, 20 Aug 2026 22:22:36 -0400 Subject: [PATCH 18/49] chore: bump GNUS-NEO-SWARM for SGProcessing bridge fixes and test vendoring Vendors SGProcessing test fixtures instead of depending on a sibling GeniusNetwork checkout, removes stale Vulkan-deadlock test skips now that the upstream fix is verified, and fixes SGProcessingBridge to return real output bytes (was returning a manifest hash) with support for windowed/overlapping input schemas. --- GNUS-NEO-SWARM | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GNUS-NEO-SWARM b/GNUS-NEO-SWARM index 9c41c18..6a66ddf 160000 --- a/GNUS-NEO-SWARM +++ b/GNUS-NEO-SWARM @@ -1 +1 @@ -Subproject commit 9c41c18489d02d12ccd9fd1968c87f36bb0f80f4 +Subproject commit 6a66ddf54754c8e560d2b09245c133858267bbfb From 418c475ae7209ac51e912a5b7330a13e171107c8 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Fri, 21 Aug 2026 17:54:23 -0400 Subject: [PATCH 19/49] Bump neo swarm --- GNUS-NEO-SWARM | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GNUS-NEO-SWARM b/GNUS-NEO-SWARM index 6a66ddf..383dd7e 160000 --- a/GNUS-NEO-SWARM +++ b/GNUS-NEO-SWARM @@ -1 +1 @@ -Subproject commit 6a66ddf54754c8e560d2b09245c133858267bbfb +Subproject commit 383dd7e8e848f8ae1e1703fd069062951b4d712d From c916f365872c31a688d0fc491778bb115dee1096 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Tue, 25 Aug 2026 18:38:20 -0400 Subject: [PATCH 20/49] docs(260825-pgu): pre-dispatch plan for MNN sgfp4-pivot / GNUS-NEO-SWARM FP4 evaluation --- .../260825-pgu-CONTEXT.md | 70 ++++++ .../260825-pgu-PLAN.md | 205 ++++++++++++++++++ .../260825-pgu-RESEARCH.md | 173 +++++++++++++++ 3 files changed, 448 insertions(+) create mode 100644 .planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-CONTEXT.md create mode 100644 .planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-PLAN.md create mode 100644 .planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-RESEARCH.md diff --git a/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-CONTEXT.md b/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-CONTEXT.md new file mode 100644 index 0000000..a12aa55 --- /dev/null +++ b/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-CONTEXT.md @@ -0,0 +1,70 @@ +# Quick Task 260825-pgu: Evaluate MNN sgfp4-pivot FP4 implementation integration with GNUS-NEO-SWARM neo workspace FP4 codec and SGProcessingManager verification layer; assess MNN-side implementation quality; scope potential new GNUS-NEO-SWARM workstream for required updates - Context + +**Gathered:** 2026-08-25 +**Status:** Ready for planning + + +## Task Boundary + +Evaluate how MNN's `sgfp4-pivot` workstream (implementing the FP4 codec described in +`.planning/sgfp4-arxiv-v2.{pdf,txt}` at `W:\gnus\GeniusNetwork\thirdparty\MNN`) integrates +with GNUS-NEO-SWARM's own FP4 implementation (`GNUS-NEO-SWARM/src/core/fp4/fp4_codec.*`) +and the SGProcessingManager/SuperGenius verification layer that sits between +GeniusCognitiveSystem and MNN (`GNUS-NEO-SWARM/src/core/sgprocessing/*`). Assess how well +the MNN side of sgfp4-pivot was executed (architecture, phase completion, test coverage, +fidelity to the arxiv spec). Determine whether GNUS-NEO-SWARM's FP4 codec is compatible +with / already updated for MNN's current sgfp4-pivot output, and identify concrete gaps. + + + + +## Implementation Decisions + +### Deliverable scope +- Produce a written evaluation report only. Do NOT scaffold a new GSD workstream as part + of this quick task. +- If the evaluation concludes a new workstream is warranted (to track GeniusCognitiveSystem + / SuperGenius / SGProcessingManager updates needed to catch up with MNN's sgfp4-pivot), + the report must say so explicitly and recommend it as a follow-up — but must not create + the workstream files itself. + +### Workstream location (if/when created later, as a follow-up — not in this task) +- Should live as a new top-level entry under `GeniusCognitiveSystem/.planning/workstreams/` + (sibling to the existing `neo` / `neo-poc` entries), pointing at the GNUS-NEO-SWARM + submodule the same way `.planning/workstreams/neo/config.json` does — not nested inside + `GNUS-NEO-SWARM/.planning/workstreams/`. + +### Claude's Discretion +- Report structure/format (sections, level of technical depth) is left to the planner/ + researcher — organize around: MNN-side quality assessment, GNUS-NEO-SWARM FP4 codec + compatibility, SGProcessingManager verification-layer implications, and a clear + yes/no recommendation on the new workstream with rationale. + + + + +## Specific Ideas + +Known landscape from initial recon (for the researcher to verify/expand, not to take on faith): +- MNN: `.planning/workstreams/sgfp4-pivot/{REQUIREMENTS,ROADMAP,STATE}.md` with 4 phases + (affine dual-mode decode core CPU uniform layouts; adaptive quadtree layout CPU mixed; + Vulkan decode uniform layouts; Vulkan decode adaptive quadtree mixed layouts). +- MNN: `.planning/quick/260821-p1q-evaluate-current-fp4-ultra-fp4-implement/SGFP4-PIVOT-ANALYSIS.md` + and `.planning/quick/260825-backfill-sgfp4-pivot-phase2-completion/` — recent related + quick-task history worth reading directly. +- GNUS-NEO-SWARM: `src/core/fp4/fp4_codec.{cpp,hpp}`, `src/core/sgprocessing/{sg_processing_bridge,tensor_interpreter}.{cpp,hpp}`, + `test/core/test_fp4_codec.cpp`, `test/integration/test_sgprocessing_pipeline.cpp`. +- Recent GNUS-NEO-SWARM history (per top-level git log) includes "SGProcessing bridge fixes + and test vendoring" (commit d229193) — worth checking whether this already accounts for + MNN's current sgfp4-pivot state. + + + + +## Canonical References + +- `W:\gnus\GeniusNetwork\thirdparty\MNN\.planning\sgfp4-arxiv-v2.txt` (and `.pdf`) — the FP4 spec MNN's sgfp4-pivot implements. +- `W:\gnus\GeniusNetwork\thirdparty\MNN\.planning\workstreams\sgfp4-pivot\{REQUIREMENTS,ROADMAP,STATE}.md` +- `GeniusCognitiveSystem\.planning\workstreams\neo\config.json` — pattern to follow if/when a new workstream is scaffolded later. + + diff --git a/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-PLAN.md b/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-PLAN.md new file mode 100644 index 0000000..0ad313e --- /dev/null +++ b/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-PLAN.md @@ -0,0 +1,205 @@ +--- +phase: quick-260825-pgu +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - .planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-EVALUATION.md +autonomous: true +requirements: + - QUICK-260825-pgu-evaluate-mnn-sgfp4-pivot +must_haves: + truths: + - "EVALUATION.md exists and delivers a decision-oriented synthesis (not a copy) of RESEARCH.md." + - "The report leads with the three-FP4-formats clarification and the 'SGFP4 is weight-compression, not an input format' distinction." + - "The report gives an explicit MNN-side quality verdict, GNUS-NEO-SWARM compatibility status, and SGProcessingManager verification-layer implications." + - "The report gives an explicit CONDITIONAL yes/no workstream recommendation and, if warranted, sites it as a new top-level GeniusCognitiveSystem/.planning/workstreams/ entry mirroring the neo config.json pattern — as a recommendation only." + - "The report lists concrete follow-up items (dead-code cleanup, stale-test fixes, ownership reconciliation) without executing any of them." + artifacts: + - ".planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-EVALUATION.md" + key_links: + - "Recommendation is grounded in the three-formats distinction and the weight-compression-vs-input-format finding, not in version drift." + - "No new workstream/config.json/ROADMAP files are created; recommendation is prose only." +--- + + +Synthesize the completed research (`260825-pgu-RESEARCH.md`) into a single, decision-oriented +evaluation document at `260825-pgu-EVALUATION.md`. The evaluation assesses (a) how well MNN's +`sgfp4-pivot` workstream was executed, (b) whether GNUS-NEO-SWARM's FP4 codec / SGProcessing +verification layer are compatible with MNN's current SGFP4 output, and (c) whether a new +GeniusCognitiveSystem workstream is warranted to close the gap. + +Purpose: Give the developer a clear, evidence-backed verdict and an actionable conditional +recommendation, so the decision on whether to open a new workstream can be made without +re-reading two full codebases. + +Output: `.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-EVALUATION.md` + +This is a WRITE-ONLY task. Per the locked CONTEXT.md decision it MUST NOT create or scaffold a +new workstream (no `.planning/workstreams/` entry, no `config.json`, no `ROADMAP.md`) — a +workstream may only be *recommended* in the report. It MUST NOT make any code changes to +GNUS-NEO-SWARM or MNN (no deleting orphaned `fp4_codec`, no fixing stale tests) — those are +follow-up items to be *listed* as recommendations, not executed. + + + +@$HOME/.claude/gsd-core/workflows/execute-plan.md + + + +@.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-CONTEXT.md +@.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-RESEARCH.md + +# The research is complete and evidence-based (both codebases read in full, concrete +# file:line evidence). Do NOT re-open the codebases to redo research — synthesize from +# RESEARCH.md. Consult a source file only to confirm a single load-bearing quote if needed. + + + + + + Task 1: Write the SGFP4 integration evaluation report + .planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-EVALUATION.md + +Write a decision-oriented evaluation that synthesizes (does not copy) `260825-pgu-RESEARCH.md` +into a document a decision-maker can act on. Preserve the concrete file:line evidence from the +research as support, but restructure around decisions rather than around the research's Q1-Q5 +question order. + +Use this section structure (exact H2 headings, since the verify step greps for them): + +1. `# SGFP4 Integration Evaluation` — title + one-line framing (date 2026-08-25). + +2. `## Verdict at a Glance` — a 4-6 bullet TL;DR. Must state up front: MNN sgfp4-pivot is + high-quality and complete; GNUS-NEO-SWARM is NOT compatible and does not reference SGFP4; + the codec in GNUS-NEO-SWARM is orphaned NF4 dead code; and the workstream recommendation is + CONDITIONAL on a scoping decision (defined below). + +3. `## The Three FP4 Formats` — LEAD the technical body with this. Explain the three distinct, + mutually-incompatible "FP4" formats and why conflating them is the central risk: + (a) GNUS-NEO-SWARM `fp4_codec` NF4-style non-uniform LUT (orphaned dead code); + (b) MNN E2M1 "Ultra FP4" behind `InputFormat::FP4_ULTRA` (the only live cross-repo FP4 path); + (c) MNN SGFP4 v2 affine dual-mode (FP4_AFFINE + T158 ternary) quadtree from the arxiv spec. + Include the key distinction that reframes the whole evaluation: SGFP4 is a MODEL-WEIGHT + compression format decoded inside MNN's graph via the `OpType_SGFP4Dequant` sidecar op — NOT + an input-tensor decode path like `dequant_fp4_packed_cpu`. State that most of the apparent + integration gap dissolves or refocuses once this is understood. Use the comparison table from + RESEARCH.md Q2 (code space / reconstruction / scale granularity / nibble order / container / + dual-mode) to make the incompatibility concrete. + +4. `## MNN-Side Implementation Quality` — deliver the verdict: HIGH quality, genuinely complete + for its locked v2-only scope. Summarize the phase-completion evidence (all 4 phases in real + source with tests; Phase 4 verified on live RTX 4070 Ti SUPER hardware), fidelity to + `sgfp4-arxiv-v2.txt` (affine w = S·c + bias, FP16 packed S+bias, self-framed 'SGF4' stream, + 5 uniform layouts + MIXED quadtree, bounds-checked), and test coverage. Then list the minor + red flags as a bulleted list, each labeled as doc-hygiene vs. deferred-perf vs. test-breadth: + stale REQUIREMENTS.md checkboxes, missing 02-VERIFICATION.md, the unrelated FP4ModelTest.cpp + build blocker, single true LAYOUT_MIXED GPU fixture, and the correctness-first (not perf-tuned) + Vulkan per-thread re-walk. Make explicit that NONE of these block correctness. + +5. `## GNUS-NEO-SWARM Compatibility` — verdict: NOT compatible and not trying to be. Explain that + `fp4_codec` is a third, unrelated NF4 format (the exact format the SGFP4 paper explicitly + rejects), that it is self-flagged orphaned dead code (D-13; consumer removed in commit + `8ee7fa4`), and that a grep for `sgfp4|affine|quadtree|t158` across GNUS-NEO-SWARM `src` + returns nothing. Conclude that the incompatibility is architectural (different integration + surface), not version drift. + +6. `## SGProcessingManager Verification-Layer Implications` — explain that the verification layer + lives in SuperGenius/SGProcessingManager (not in the GNUS-NEO-SWARM files read), that the + bridge is a thin client and TensorInterpreter only ever sees already-dequantized FLOAT32, and + that there is NO silent-mis-verify risk today because no `InputFormat::SGFP4` enum exists — an + SGFP4 job simply cannot be expressed and would be rejected at the format gate. Surface the two + unresolved / flagged items honestly: (a) the FP4_ULTRA live-decode-vs-stub ambiguity (MEDIUM + confidence, SuperGenius repo not read this session) and (b) the stale, self-contradicting + `test_sg_connectivity.cpp` fp4_ultra assertion (static inference, not executed). Note SGFP4 is + a separate concern from FP4_ULTRA — resolving the E2M1 wiring does NOT address SGFP4. + +7. `## Integration Gaps` — present the two conditional gap sets from RESEARCH.md Q4 as clearly + separated lists: "If SGFP4 stays a model-weight concern (likely correct reading)" (MNN build + must include `OpType_SGFP4Dequant` + `MNN_SUPPORT_TRANSFORMER_FUSE`; model export to SGFP4 + sidecars belongs to gnus-poc; bridge/interpreter need little-to-no change; orphaned `fp4_codec` + should be deleted) vs. "Additionally, if GCS/SuperGenius must speak SGFP4 as an input/wire + format" (new `InputFormat::SGFP4_V2`, new format-map cases, new ProcessingManager/MNN_Tensor + validation branch, and the arxiv §8 verifiable-execution/attestation story which has no anchor + anywhere yet). Also call out the ownership-reconciliation gap: GCS `neoswarm/ROADMAP.md:173` + still assigns SGFP4 GPU decode shaders to GNUS-NEO-SWARM, but MNN already built them — that + roadmap row is now duplicative/obsolete and needs an explicit decision. + +8. `## Workstream Recommendation` — give an EXPLICIT CONDITIONAL recommendation, not an + unconditional one. State the condition as a scoping decision the developer must make: + - If the goal is "run SGFP4-quantized models through MNN via the existing SGProcessing path": + a dedicated workstream is NOT warranted — it is a handful of quick tasks (confirm/bump the + linked MNN build to include `OpType_SGFP4Dequant`; add SGFP4 export in gnus-poc; one + end-to-end SGFP4-weighted-model test; a cleanup task for the orphaned NF4 codec + two stale + tests). + - If the goal is the full cross-repo story (GCS/SuperGenius speaking SGFP4 as a first-class + format and/or wiring the arxiv §8 verifiable-execution/attestation use case): YES, a + dedicated workstream IS warranted — it spans three repos with real sequencing/dependencies + and touches the reputation/consensus verification layer. + Include the non-binding P1-P4 phase sketch from RESEARCH.md Q5 for the full-workstream case. + Then state the SITING recommendation explicitly and as a recommendation ONLY: if/when created + later, it should live as a new top-level `GeniusCognitiveSystem/.planning/workstreams/` entry + (sibling to `neo`/`neo-poc`), pointing at the GNUS-NEO-SWARM submodule the same way + `.planning/workstreams/neo/config.json` does (submodule + planning_root + workstream_name), + NOT nested inside `GNUS-NEO-SWARM/.planning/workstreams/`. Add an explicit sentence stating + this task does NOT create that workstream and only recommends it. + +9. `## Recommended Follow-Ups` — a checklist of concrete, listed-not-executed items: delete + orphaned `fp4_codec.{hpp,cpp}` + `test/core/test_fp4_codec.cpp`; fix the stale + `test_sg_connectivity.cpp` fp4_ultra assertion; reconcile the ROADMAP SGFP4-shader ownership + row; resolve the FP4_ULTRA live-vs-stub ambiguity against the SuperGenius repo; MNN-side doc + hygiene (REQUIREMENTS.md checkboxes, missing 02-VERIFICATION.md). Prefix the section with one + sentence stating these are recommendations for future work and are intentionally NOT performed + in this evaluation task. + +10. `## Confidence and Caveats` — carry forward the research's confidence posture: HIGH on both + codebases' source and the three-formats finding; MEDIUM on SuperGenius/SGProcessingManager + internals (repo not read this session); the FP4_ULTRA live-vs-stub ambiguity is unresolved; + the stale-test failure is a static inference; no builds or tests were run. + +Writing constraints: +- Synthesize and reorganize around decisions; do not paste RESEARCH.md verbatim. Keep concrete + file:line evidence as inline support where it strengthens a claim. +- Do NOT use scope-reduction language (no "v1/v2 simplification", "for now", "placeholder") when + describing the recommendation — split the recommendation by scope condition as instructed above. +- Honor CONTEXT.md locked decisions: report only; recommend the workstream, do not create it; + list follow-ups, do not execute them. Do not touch any file other than the EVALUATION.md. + + + f=".planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-EVALUATION.md"; test -f "$f" || { echo "MISSING FILE"; exit 1; }; for h in "The Three FP4 Formats" "MNN-Side Implementation Quality" "GNUS-NEO-SWARM Compatibility" "Verification-Layer Implications" "Integration Gaps" "Workstream Recommendation" "Recommended Follow-Ups"; do grep -q "$h" "$f" || { echo "MISSING HEADING: $h"; exit 1; }; done; grep -qi "conditional" "$f" || { echo "MISSING conditional recommendation"; exit 1; }; grep -q "workstreams/neo" "$f" || { echo "MISSING neo config siting reference"; exit 1; }; test ! -e .planning/workstreams/sgfp4 || { echo "ERROR: workstream was scaffolded (forbidden)"; exit 1; }; echo "OK" + + +`260825-pgu-EVALUATION.md` exists with all ten required sections; leads with the three-formats +clarification and the weight-compression-vs-input-format distinction; states the MNN quality +verdict (HIGH/complete), the GNUS-NEO-SWARM incompatibility (orphaned NF4 dead code), the +verification-layer implications, the two conditional gap sets, and an explicit CONDITIONAL +workstream recommendation with the top-level `.planning/workstreams/` siting stated as a +recommendation only. No workstream files, config.json, ROADMAP.md, or code changes were created. + + + + + + +- `260825-pgu-EVALUATION.md` exists and contains all required H2 sections (automated grep). +- Recommendation is explicitly conditional and cites the neo `config.json` siting pattern. +- No new `.planning/workstreams/` entry, `config.json`, or `ROADMAP.md` was created. +- No source files in GNUS-NEO-SWARM or MNN were modified (git status shows only the new + EVALUATION.md under the quick-task directory). + +Note: No `` — this plan produces a single Markdown document with no code, no +package installs, and no runtime attack surface, so STRIDE analysis is not applicable. + + + +- A single evaluation document synthesizes the research into an actionable, decision-oriented + verdict covering all five required dimensions plus a conditional workstream recommendation. +- The locked CONTEXT.md decisions are honored: report only, recommend-don't-create the + workstream, list-don't-execute the follow-ups. + + + +Write `.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-EVALUATION.md`. +(No SUMMARY file required for this quick task unless the quick orchestrator requests one.) + diff --git a/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-RESEARCH.md b/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-RESEARCH.md new file mode 100644 index 0000000..fa30275 --- /dev/null +++ b/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-RESEARCH.md @@ -0,0 +1,173 @@ +# Quick Task 260825-pgu — Research: MNN sgfp4-pivot ⇄ GNUS-NEO-SWARM FP4 / SGProcessing integration + +**Researched:** 2026-08-25 +**Domain:** FP4/SGFP4 weight-quantization decode; MNN Execution ops; GNUS-NEO-SWARM ⇄ SGProcessingManager bridge +**Confidence:** HIGH on both codebases' source (read in full); MEDIUM on SuperGenius/SGProcessingManager internals (read only via the MNN-side analysis excerpts and NEO-SWARM plan docs — the SuperGenius repo itself was not in the read set). No builds or tests were run this session; a few findings are static inferences and are flagged as such. + +--- + +## Executive Summary + +There are **three distinct, mutually-incompatible "FP4" formats** in this repo family, and conflating them is the central risk this evaluation must clear up: + +1. **GNUS-NEO-SWARM `fp4_codec`** (`GNUS-NEO-SWARM/src/core/fp4/fp4_codec.{hpp,cpp}`) — an **NF4-style non-uniform 16-value lookup-table** codec (QLoRA NF4 lineage). Per-64×64-macroblock symmetric scale, **no bias**, high-nibble = even index. It is **orphaned dead code**: its own header self-flags it (D-13), and its only consumer reference was removed from `MNNInferenceEngine` (commit `8ee7fa4`). It is **not** SGFP4 and **not** MNN's E2M1. +2. **MNN E2M1 "Ultra FP4"** (`include/MNN/FP4DequantUtils.hpp::dequant_fp4_packed_cpu`) — an E2M1 float microformat behind `InputFormat::FP4_ULTRA`. This is the **only live cross-repo FP4 path today**: GCS → `SGProcessingBridge` (FP4_ULTRA) → SGProcessingManager → MNN's E2M1 decode. +3. **MNN SGFP4 v2** (the `sgfp4-pivot` workstream, new `OpType_SGFP4Dequant`) — the affine-integer dual-mode (FP4_AFFINE + T158 ternary) quadtree container from `sgfp4-arxiv-v2.txt`. **Complete on CPU + Vulkan, well-tested, verified on live GPU hardware — but consumed by nothing on the GNUS-NEO-SWARM / GCS / SuperGenius side.** + +**Primary conclusion:** MNN's sgfp4-pivot is a high-quality, genuinely-complete implementation. GNUS-NEO-SWARM is **not** compatible with it and does not reference it in any way — not because of a version drift, but because SGFP4 is a **model-weight compression format decoded inside MNN's graph via an external sidecar op**, which is a fundamentally different integration surface than the **input-tensor** `FP4_ULTRA` pass-through that GNUS-NEO-SWARM's bridge currently speaks. The integration gap is real but its *shape* depends on a prerequisite architecture decision (is SGFP4 a model-weight concern owned by MNN + the export pipeline, or does GCS/SuperGenius need to speak it directly?). A dedicated workstream is warranted **only** for the full cross-repo story; the minimal "run SGFP4-quantized models through MNN" path is closer to a few quick tasks plus a dead-code cleanup. + +--- + +## Q1 — MNN-side implementation quality + +**Verdict: HIGH quality, genuinely complete for its locked v2-only scope.** All 4 phases are implemented in real source (not just planning docs), with real tests, and Phase 4 was independently verified on live GPU hardware. + +### Phase completion — docs vs. actual code + +| Phase | Roadmap status | Code evidence | Real? | +|-------|----------------|---------------|-------| +| 1 — Affine dual-mode CPU, uniform | Complete (2026-08-24) | `include/MNN/SGFP4DequantUtils.hpp` (460 lines), `source/backend/cpu/CPUSGFP4Dequant.{cpp,hpp}`, `source/shape/ShapeSGFP4Dequant.cpp`, registered in `CPUOPRegister.cpp` / `ShapeRegister.cpp` | Yes | +| 2 — Adaptive quadtree CPU, MIXED | Complete (2026-08-24) | `sgfp4_walk_quadtree` + MIXED branch in `dequant_sgfp4_container_cpu` (`SGFP4DequantUtils.hpp:188-222, 387-408`); encoder `tools/fp4/encode_sgfp4.py` (1085 lines) | Yes | +| 3 — Vulkan uniform | Complete (2026-08-25) | `source/backend/vulkan/buffer/execution/glsl/sgfp4_dequant.comp`, `VulkanSGFP4Dequant.{cpp,hpp}`, regenerated `AllShader.cpp/.h` + `VulkanShaderMap.cpp` | Yes | +| 4 — Vulkan MIXED | Complete (2026-08-25) | `locateMixedLeaf` in `sgfp4_dequant.comp:114-180`; `04-VERIFICATION.md` reports live run on RTX 4070 Ti SUPER, 14 fixtures green | Yes | + +### Fidelity to `sgfp4-arxiv-v2.txt` + +The CPU decoder (`SGFP4DequantUtils.hpp`) is a faithful, section-by-section implementation of the normative spec: +- **Affine reconstruction `w = S·c + bias`** for both modes — `sgfp4_decode_leaf_payload` (`:262-282`). Mode 0 FP4_AFFINE two's-complement `[-8,7]` via `(nib ^ 0x8) - 0x8`; Mode 1 T158 ternary `00→0, 01→+1, 10→-1, 11→0(reserved)`. Matches spec §3.2 / §4.3 Eq.3/4 and the reserved-symbol rule exactly. +- **FP16 packed S+bias with the v2 12-bit truncated-bias recovery** `S=half(h>>16)`, `bias=half(h & 0xFFF0)`, `flags=h & 0xF` — `unpack_leaf_header` (`:232-244`), using the vendored `half_float::half` (no hand-rolled FP16). Matches §6.2 Eq.6. +- **Self-framed stream** magic `'SGF4'` / version `0x02` / `B` / 16-byte-aligned record-offset table / `align16(16+4B)` region start — `dequant_sgfp4_container_cpu:330-360`. Matches §6.1. +- **All 5 uniform layouts** (`sgfp4_resolve_uniform_layout:105-135`) + **LAYOUT_MIXED quadtree** pre-order DFS TL/TR/BL/BR split-map walk (`sgfp4_walk_quadtree:188-222`). Matches §6.2 Table 3. +- Bounds-checked on every read (ASVS V5 posture), rejects malformed containers with `false` rather than OOB. + +The Vulkan shader (`sgfp4_dequant.comp`) is a documented bit-for-bit port of the CPU walker (constants ported 1:1; `unpackHalf2x16` for FP16). `04-VERIFICATION.md` cross-checked the GLSL split-map read formula against the CPU `SGFP4SplitMapReader` line-by-line. + +### Test coverage (real, but note the caveats) +- `test/op/SGFP4DequantTest.cpp` (866 lines) — two suites: `op/sgfp4/uniform_decode` (cross-language fixture round-trip for both modes × all 5 uniform layouts, degenerate MIXED smoke, hand-built TL/TR/BL/BR golden traversal, ternary reserved-symbol, FP16 header precision incl. 0xFFF0 mask, malformed-container negatives, op-level end-to-end via external sidecar) and `op/sgfp4/mixed_decode` (independent golden enumerator that shares no code with the decoder — D-05, mixed round-trip, split-map negative cases). +- `test/op/SGFP4VulkanDequantTest.cpp` (190 lines) — `op/sgfp4/vulkan_uniform_parity`: CPU-reference-vs-Vulkan for all fixtures within rtol, graceful skip when no Vulkan device. +- `tools/fp4/encode_sgfp4.py` — real error-driven quadtree encoder (`subdivide_macroblock`, `build_split_map`, `classify_layout`, `apply_ternary_veto`, mode-select ε=0.10), a reference decoder, a `selftest()`, and a C++ fixture emitter. + +### Red flags (all minor / documentation-level, none blocking correctness) +1. **Stale REQUIREMENTS.md checkboxes.** `REQUIREMENTS.md:25-28` shows SGV2-08…11 (Phase 2 CPU quadtree) *unchecked* and the bottom traceability table (`:73-76`) lists them "Pending" — yet the code fully implements them. `STATE.md:52-55` explicitly acknowledges this is stale-doc debt, and the `260825-backfill-sgfp4-pivot-phase2-completion` quick task confirmed via git history (commits `1c9e5633`/`b2a83969`) that Phase 2 was really executed. Not a code gap; a doc-hygiene gap. +2. **No `02-VERIFICATION.md`** (Phases 1, 3, 4 have one). Phase 2 completion is evidenced only by SUMMARY files + commits. Doc debt. +3. **`test/op/FP4ModelTest.cpp` blocks a from-scratch `run_test.out` build** — pre-existing dead code from the *unrelated* `milestone` workstream (commit `cffaf4bd`), not sgfp4's fault (`deferred-items.md`). Verifiers worked around it with a temp neutral stub; it remains unfixed. +4. **Modest MIXED GPU parity breadth** — the 14-fixture Vulkan sweep contains exactly **one** true LAYOUT_MIXED fixture (`mixed_asymmetric`, `SGFP4DequantFixtures.h:137`). The quadtree-on-GPU path is exercised, but by a single tree shape. +5. **Vulkan decode is correctness-first, not perf-tuned.** `locateElement` does a full **per-thread re-walk** of the record/leaf structure for every output element (O(records × leaves) per element), rather than the spec's "one workgroup per macroblock" shared-memory model. This is functionally correct and deliberately deferred (SGV2-18, a v2 requirement); flag it if throughput ever matters. + +**Deliberately out of scope in MNN (locked decisions, not defects):** SGFP4 v1 fixed-payload profile (v2-only); attestation / bit-exact conformance vectors (SuperGenius's job); SuperGenius-side integration. + +--- + +## Q2 — GNUS-NEO-SWARM FP4 codec compatibility + +**Verdict: NOT compatible, and not even trying to be — GNUS-NEO-SWARM's `fp4_codec` is a third, unrelated format and is orphaned dead code. Nothing in GNUS-NEO-SWARM calls MNN's sgfp4-pivot decode paths.** + +### `fp4_codec` is a different format from BOTH SGFP4 and E2M1 + +| Dimension | GNUS-NEO-SWARM `fp4_codec` | MNN SGFP4 v2 | MNN E2M1 "Ultra" | +|-----------|---------------------------|--------------|------------------| +| Code space | NF4 non-uniform 16-value LUT `kFP4LUT[16]` (`fp4_codec.hpp:32-33`) | Affine integer: FP4 `[-8,7]` + ternary `{-1,0,1}` | E2M1 float (±0.5…±3, Inf/NaN) | +| Reconstruction | `LUT[idx] * scale` (`fp4_codec.cpp:53-56`) | `S·c + bias` (real FP16 bias) | table lookup, per-channel scale, bias≡0 | +| Scale granularity | 1 FP32 scale per 64×64 macroblock (`fp4_codec.cpp:124`) | FP16 S+bias per leaf (down to 4×4) | per-output-channel FP32 (symmetricQuan) | +| Nibble order | **high nibble = even index** (`fp4_codec.cpp:156-159`) | code i at bit `4·(i mod 8)` → **low nibble = index 0** | low nibble = even index | +| Container | flat `data_/scales_` arrays, no header | self-framed `'SGF4'` stream, quadtree | MNN `symmetricQuan` flat arrays | +| Dual-mode / ternary | none | core feature | none | + +The SGFP4 paper (`sgfp4-arxiv-v2.txt:73-76`) *explicitly contrasts itself against NF4*: "QLoRA's NF4 uses a 16-entry non-uniform codebook… SGFP4 deliberately uses affine integer codes instead." GNUS-NEO-SWARM's codec **is** the NF4 approach the spec rejects — even the opposite nibble order means the packed bytes are not interchangeable. + +### It is orphaned dead code +`fp4_codec.hpp:8-13` self-documents: *"Flagged per Phase 4 D-13: this NF4-style codec predates and does not match MNN_Ultra's E2M1 target format for InputFormat::FP4_ULTRA. MNNInferenceEngine's reference to this class was confirmed orphaned and removed (Phase 4 plan 04-04). This class is a candidate for removal."* Commit `8ee7fa4` ("fix(04-04): remove orphaned fp4_codec member from MNNInferenceEngine") did the removal. `test/core/test_fp4_codec.cpp` (6 tests) still exists and tests the dead class in isolation. Grep confirms **zero** references to `fp4_codec` from `mnn_inference_engine.cpp` or anywhere else in live `src/`. + +### Nothing consumes MNN's SGFP4 +Grep for `sgfp4|affine|quadtree|t158` across `GNUS-NEO-SWARM/src` returns **nothing**. `mnn_inference_engine.cpp` has no SGFP4 awareness; for FP4 it only sets `input_fmt = FP4_ULTRA` (`:266-267`) and hands off to the bridge. The one live FP4 path (FP4_ULTRA) targets MNN's **E2M1** `dequant_fp4_packed_cpu`, not SGFP4. + +--- + +## Q3 — SGProcessingManager verification-layer implications + +**Verdict: The verification layer is in SuperGenius/SGProcessingManager, not in the GNUS-NEO-SWARM files read here. On the GNUS-NEO-SWARM side there is nothing that would silently mis-verify SGFP4 mixed-layout output — because there is no SGFP4 path at all; an SGFP4 job simply cannot be expressed today.** + +### What the GNUS-NEO-SWARM "bridge" side actually does +- `SGProcessingBridge` (`sg_processing_bridge.cpp`) is a **thin client**: it builds GNUS-schema JSON (`BuildSchemaJson`) and submits a job (`SubmitDirect` → `ProcessingManager::Create`/`Process`, `:308-380`). It performs **no** FP4 decode and **no** verification itself. Decode + verification happen inside SGProcessingManager (SuperGenius repo). +- `TensorInterpreter` (`tensor_interpreter.cpp`) operates on the **output** bytes, which are already-dequantized FLOAT32 by the time they return (`:54-56`: *"FP4_ULTRA output from SGProcessingManager is already dequantized to FLOAT32"*). It never sees an FP4/SGFP4 container, so new tensor layouts from adaptive-quadtree decode would not reach it. `InferViaSGProcessing` calls `Interpret(..., FLOAT32)` unconditionally (`mnn_inference_engine.cpp:275`). + +### Why there is no "silent mis-verify" risk today +There is **no `InputFormat::SGFP4`/`SGFP4_V2` enum**. `InputFormatToTypeString` / `InputFormatToFormatString` (`sg_processing_bridge.cpp:40-95`) only know FLOAT32/16, INT8/16/32, RGB(A)8, FP4_ULTRA. An SGFP4 job cannot be labeled, so it would be rejected at `DataType::from_json` / the processor format gate rather than mis-verified. Today's whole FP4 story assumes E2M1/FP4_ULTRA. + +### Cross-repo verification status is genuinely ambiguous (flag for follow-up) +There is **conflicting evidence** on whether SGProcessingManager currently *decodes* FP4_ULTRA live or still returns a structured stub: +- MNN-side `SGFP4-PIVOT-ANALYSIS.md:126-142` says FP4_ULTRA decode is **wired live** (`processing_processor_mnn_tensor.cpp` calls `MNN::dequant_fp4_packed_cpu`, SuperGenius commit `e1f28d7`), and flags a **stale SuperGenius test** `Fp4UltraRecognizedButDecodeUnavailable` asserting the old `FORMAT_UNSUPPORTED` behavior. +- NEO-SWARM `neoswarm/phases/04-sgprocessing-integration/04-02-PLAN.md:19-31` describes FP4_ULTRA as **"wire + stub"** returning `ProcessingError{FORMAT_UNSUPPORTED}` because MNN's E2M1 kernel was 39 commits diverged on `MNN_Ultra` at that time (D-04/D-08). +These two are from different dates/branches and I could not reconcile them without reading the SuperGenius repo (not in scope this session). **Whichever is current, note that SGFP4 is a separate concern from FP4_ULTRA — resolving the E2M1 wiring does not address SGFP4.** + +### Bonus finding — a stale, self-contradicting GNUS-NEO-SWARM test (static inference, not run) +`test/integration/test_sg_connectivity.cpp:63-72` (`BuildSchemaJsonFP4UltraFormatEmitsFP4Type`) asserts the schema JSON **contains** the lowercase literal `"fp4_ultra"` (`EXPECT_NE(find("fp4_ultra"), npos)`). But Phase 04-01 (`83a0e84`) deliberately changed the bridge so the type field is `"tensor"` and only the *format* field is `"FP4_ULTRA"` (uppercase). A case-sensitive `find("fp4_ultra")` now returns `npos`, so this `EXPECT_NE` should **fail**. It directly contradicts `test/integration/test_sgprocessing_pipeline.cpp:96` which asserts `find("fp4_ultra") == npos`. Looks like 04-01 updated one test and left the connectivity test stale. Worth a quick fix regardless of the SGFP4 decision. (Confidence: MEDIUM — inferred statically, not executed.) + +--- + +## Q4 — Integration gaps (concrete, file-level) + +The key architectural clarification that reshapes this list: **MNN's SGFP4 is a weight-compression format, consumed as a graph op (`OpType_SGFP4Dequant`) that loads compressed weights from an external `.mnn.weight`-style sidecar** (`CPUSGFP4Dequant::onResize:43-89`, `VulkanSGFP4Dequant` creator `:124-205`). It is **not** an input-tensor decode entry point like `dequant_fp4_packed_cpu`. So "consuming SGFP4" most likely means *running a model whose weights were exported to SGFP4*, not *feeding SGFP4 as an input tensor through the FP4_ULTRA slot*. That distinction determines which of the gaps below actually apply. + +**If SGFP4 stays a model-weight concern (likely correct reading):** +1. **MNN submodule / build** must include `OpType_SGFP4Dequant` + `MNN_SUPPORT_TRANSFORMER_FUSE` (the op and its tests are gated on this macro). GNUS-NEO-SWARM links MNN; confirm the linked build has the op registered. +2. **Model export** to SGFP4 sidecars — per GCS ROADMAP this belongs to `gnus-poc` ("SGFP4 quantization export | gnus-poc | poc (Python)", `neoswarm/ROADMAP.md:161`). `tools/fp4/encode_sgfp4.py` is the reference encoder to build on. +3. **`SGProcessingBridge` / `TensorInterpreter` need little-to-no change** — output is still FLOAT32; the container lives with the model, not the input. +4. **`fp4_codec.{hpp,cpp}` + `test/core/test_fp4_codec.cpp` should be deleted** (orphaned NF4 dead code, self-flagged D-13). + +**Additionally, if GCS/SuperGenius must speak SGFP4 as an input/wire format (only if that's a real requirement):** +5. New `InputFormat::SGFP4_V2` in SuperGenius `generated/InputFormat.hpp`. +6. New cases in `InputFormatToTypeString` / `InputFormatToFormatString` (`sg_processing_bridge.cpp:40-95`). +7. New SGFP4 branch in SGProcessingManager's `ProcessingManager::CheckProcessValidity` + `MNN_Tensor` processor (currently only FP4_ULTRA/E2M1, per `04-02-PLAN.md`). +8. The **verifiable-execution / attestation** story (arxiv §8: bit-exact cross-device replay, ternary integer-exact path) — currently has **no anchor anywhere** in the codebase family (`SGFP4-PIVOT-ANALYSIS.md:146`); would be new work in both MNN and SuperGenius if pursued. + +**Ownership reconciliation gap (important):** GCS `neoswarm/ROADMAP.md:173` assigns *"SGFP4 GPU decode shaders (Vulkan/MoltenVK)"* to GNUS-NEO-SWARM — but MNN's sgfp4-pivot has now built exactly that inside MNN (`sgfp4_dequant.comp`). This roadmap line is now duplicative/obsolete and needs an explicit decision: does GNUS-NEO-SWARM consume MNN's SGFP4 Vulkan decode, or was that ownership row superseded? + +**Housekeeping gaps:** stale `test_sg_connectivity.cpp` fp4_ultra assertion (Q3); stale SuperGenius `Fp4UltraRecognizedButDecodeUnavailable` test (cross-repo); MNN-side stale REQUIREMENTS checkboxes + missing `02-VERIFICATION.md` (Q1). + +--- + +## Q5 — Workstream recommendation input + +**Is a dedicated new workstream warranted? Conditional — and the condition is a scoping decision the report should surface, not pre-empt.** + +- **If the goal is "run SGFP4-quantized models through MNN via the existing SGProcessing path":** this is **NOT** workstream-sized. It's a handful of quick tasks: (a) confirm/bump the linked MNN build to include `OpType_SGFP4Dequant`; (b) add SGFP4 export in `gnus-poc`; (c) one end-to-end test running an SGFP4-weighted model; (d) a cleanup task deleting the orphaned NF4 `fp4_codec` + fixing the two stale FP4 tests. Bridge/interpreter are essentially unchanged because decode is internal to MNN and output is FLOAT32. + +- **If the goal is the full cross-repo story** (GCS/SuperGenius speaking SGFP4 as a first-class format **and/or** wiring the arxiv §8 verifiable-execution/attestation use case): **yes, a dedicated workstream is warranted** — it spans three repos (poc export, MNN submodule integration, SuperGenius InputFormat + processor + verification), has genuine sequencing/dependencies, and touches the reputation/consensus verification layer that is the paper's whole motivation. + +**Rough phase breakdown if the full workstream is chosen** (non-binding sketch; per CONTEXT.md, do NOT scaffold it in this task — recommend as follow-up, sited as a top-level `GeniusCognitiveSystem/.planning/workstreams/` entry pointing at the GNUS-NEO-SWARM submodule the way `.planning/workstreams/neo/config.json` does): +- **P1 — Reconcile & clean up:** delete orphaned NF4 `fp4_codec` + its test; fix stale `test_sg_connectivity.cpp` and (cross-repo) SuperGenius stale test; resolve the ROADMAP ownership row (MNN owns SGFP4 decode vs. GNUS-NEO-SWARM shaders); pin MNN submodule to a commit that includes `OpType_SGFP4Dequant`. +- **P2 — SGFP4 model export + MNN graph consumption:** `gnus-poc` exports weights to SGFP4 sidecars (build on `encode_sgfp4.py`); end-to-end inference of an SGFP4-weighted model through MNN LLM / SGProcessing; output-parity check. +- **P3 (only if SGFP4-as-input is required):** SuperGenius `InputFormat::SGFP4_V2` + bridge mapping + `ProcessingManager`/`MNN_Tensor` validation branch. +- **P4 (optional, arxiv §8):** verifiable-execution/attestation — bit-exact replay + ternary integer-exact determinism class; net-new in both MNN and SuperGenius. + +**Recommended framing for the report author:** lead with the three-formats clarification and the "SGFP4 is weight-compression, not an input format" distinction — most of the apparent "integration gap" dissolves or refocuses once that is stated. Then give the conditional yes/no above rather than an unconditional one. + +--- + +## Evidence Index (file:line) + +**MNN sgfp4-pivot (`W:\gnus\GeniusNetwork\thirdparty\MNN`):** +- Spec: `.planning/sgfp4-arxiv-v2.txt` (§3.2 affine dual-mode; §4.3 packing; §6.1/6.2 v2 framing/records; §8 verifiable execution) +- Workstream docs: `.planning/workstreams/sgfp4-pivot/{REQUIREMENTS.md, ROADMAP.md, STATE.md}`; `SGFP4-PIVOT-ANALYSIS.md`; `260825-backfill-sgfp4-pivot-phase2-completion/SUMMARY.md`; `phases/01…/deferred-items.md`; `phases/04…/04-VERIFICATION.md` +- CPU decode core: `include/MNN/SGFP4DequantUtils.hpp:105-135` (uniform resolver), `:188-222` (quadtree walk), `:232-244` (FP16 unpack), `:262-282` (dual-mode payload), `:321-456` (container decode) +- CPU op: `source/backend/cpu/CPUSGFP4Dequant.cpp:43-122` +- Vulkan: `source/backend/vulkan/buffer/execution/glsl/sgfp4_dequant.comp:114-180, 189-292`; `VulkanSGFP4Dequant.cpp:124-211` +- Encoder: `tools/fp4/encode_sgfp4.py` (subdivide/build_split_map/classify/veto/selftest/fixture-emit) +- Tests: `test/op/SGFP4DequantTest.cpp` (uniform_decode + mixed_decode suites); `test/op/SGFP4VulkanDequantTest.cpp`; `test/op/SGFP4DequantFixtures.h` +- E2M1 contrast: `include/MNN/FP4DequantUtils.hpp::dequant_fp4_packed_cpu` + +**GNUS-NEO-SWARM (`W:\gnus\GeniusCognitiveSystem\GNUS-NEO-SWARM`):** +- NF4 codec (orphaned): `src/core/fp4/fp4_codec.hpp:8-13, 32-33`; `fp4_codec.cpp:53-56, 124, 156-159`; test `test/core/test_fp4_codec.cpp` +- Bridge: `src/core/sgprocessing/sg_processing_bridge.cpp:40-95` (format maps), `:308-380` (SubmitDirect) +- Interpreter: `src/core/sgprocessing/tensor_interpreter.cpp:44-60` +- Engine: `src/core/engine/mnn_inference_engine.cpp:257-288` (InferViaSGProcessing; FP4_ULTRA label at `:266-267`) +- Stale test: `test/integration/test_sg_connectivity.cpp:63-72` vs `test/integration/test_sgprocessing_pipeline.cpp:96` +- Phase 04 integration history: `.planning/workstreams/neoswarm/phases/04-sgprocessing-integration/04-0{1,2}-PLAN.md`; ROADMAP ownership rows `.planning/workstreams/neoswarm/ROADMAP.md:161,173` + +**GCS top-level:** `.planning/SUBREPOS.md`; `.planning/intel/classifications/sgfp4-format*.json`; `.planning/workstreams/neo/config.json` + +## Confidence & caveats +- HIGH: both codebases' source (read in full), MNN phase completion, the three-formats finding, fp4_codec being orphaned. +- MEDIUM: SuperGenius/SGProcessingManager internals (secondhand via MNN analysis + NEO-SWARM plan docs; the SuperGenius repo was not read this session); the FP4_ULTRA live-vs-stub ambiguity is unresolved; the stale `test_sg_connectivity.cpp` failure is a static inference (not executed). +- No builds or tests were run. From f1a6b46841c307c9551f8df703a80742f3ff511c Mon Sep 17 00:00:00 2001 From: itsafuu Date: Tue, 25 Aug 2026 18:41:23 -0400 Subject: [PATCH 21/49] docs(260825-pgu): SGFP4 integration evaluation report - Synthesizes RESEARCH.md into a decision-oriented verdict - MNN sgfp4-pivot: HIGH quality, complete for v2-only scope - GNUS-NEO-SWARM: NOT compatible; fp4_codec is orphaned NF4 dead code - Conditional workstream recommendation with neo config.json siting pattern --- .../260825-pgu-EVALUATION.md | 277 ++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 .planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-EVALUATION.md diff --git a/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-EVALUATION.md b/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-EVALUATION.md new file mode 100644 index 0000000..6734613 --- /dev/null +++ b/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-EVALUATION.md @@ -0,0 +1,277 @@ +# SGFP4 Integration Evaluation + +Evaluation of MNN's `sgfp4-pivot` workstream against GNUS-NEO-SWARM's FP4 codec and the +SGProcessingManager verification layer, and a conditional recommendation on whether a new +GeniusCognitiveSystem workstream is warranted to close the gap. (2026-08-25) + +## Verdict at a Glance + +- **MNN's `sgfp4-pivot` is high-quality and genuinely complete** for its locked v2-only scope: + all 4 phases exist in real source with real tests, and Phase 4 was verified on live GPU + hardware (RTX 4070 Ti SUPER). +- **GNUS-NEO-SWARM is NOT compatible with MNN's SGFP4** and does not reference it anywhere — + not because of version drift, but because it is architecturally not built to. +- **The FP4 codec already inside GNUS-NEO-SWARM (`fp4_codec.{hpp,cpp}`) is orphaned NF4 dead + code** — a third, unrelated format that the SGFP4 spec itself explicitly rejects as a design. +- Most of the apparent "integration gap" dissolves once one fact is understood: **SGFP4 is a + model-weight compression format decoded inside MNN's graph, not an input-tensor format** like + the `FP4_ULTRA` path GNUS-NEO-SWARM currently speaks. +- **The workstream recommendation is CONDITIONAL** on a scoping decision the developer must + make (see "Workstream Recommendation" below) — it is not a blanket yes or no. +- No code was changed and no workstream was scaffolded as part of producing this report; all + actions below are recommendations only. + +## The Three FP4 Formats + +The single biggest risk in reasoning about this integration is conflating three distinct, +mutually-incompatible things that all happen to be called "FP4": + +1. **GNUS-NEO-SWARM `fp4_codec`** (`GNUS-NEO-SWARM/src/core/fp4/fp4_codec.{hpp,cpp}`) — an + NF4-style non-uniform 16-value lookup-table codec (QLoRA NF4 lineage). It is orphaned dead + code today (see "GNUS-NEO-SWARM Compatibility" below). +2. **MNN E2M1 "Ultra FP4"** (`include/MNN/FP4DequantUtils.hpp::dequant_fp4_packed_cpu`), reached + through `InputFormat::FP4_ULTRA`. This is the **only live cross-repo FP4 path today**: GCS → + `SGProcessingBridge` (labels the job `FP4_ULTRA`) → SGProcessingManager → MNN's E2M1 decode. +3. **MNN SGFP4 v2** (the `sgfp4-pivot` workstream's subject, new `OpType_SGFP4Dequant`) — the + affine-integer dual-mode (FP4_AFFINE + T158 ternary) quadtree container defined in + `sgfp4-arxiv-v2.txt`. Complete on CPU + Vulkan, well-tested, verified on live GPU hardware — + but consumed by nothing on the GNUS-NEO-SWARM / GCS / SuperGenius side. + +The distinction that reframes the whole evaluation: **SGFP4 is a model-weight compression +format, decoded inside MNN's graph via the external sidecar op `OpType_SGFP4Dequant`** — it is +not an input-tensor decode path like `dequant_fp4_packed_cpu`. Consuming SGFP4 most likely means +*running a model whose weights were exported to SGFP4*, not *feeding SGFP4 bytes through the +FP4_ULTRA input slot*. Once that is understood, most of the apparent integration gap either +dissolves (no bridge/interpreter changes needed) or refocuses onto a much narrower question +(does the linked MNN build register the op, and does something export SGFP4 weights). + +| Dimension | GNUS-NEO-SWARM `fp4_codec` | MNN SGFP4 v2 | MNN E2M1 "Ultra" | +|-----------|---------------------------|--------------|------------------| +| Code space | NF4 non-uniform 16-value LUT `kFP4LUT[16]` (`fp4_codec.hpp:32-33`) | Affine integer: FP4 `[-8,7]` + ternary `{-1,0,1}` | E2M1 float (±0.5…±3, Inf/NaN) | +| Reconstruction | `LUT[idx] * scale` (`fp4_codec.cpp:53-56`) | `S·c + bias` (real FP16 bias) | table lookup, per-channel scale, bias≡0 | +| Scale granularity | 1 FP32 scale per 64×64 macroblock (`fp4_codec.cpp:124`) | FP16 S+bias per leaf (down to 4×4) | per-output-channel FP32 (symmetricQuan) | +| Nibble order | high nibble = even index (`fp4_codec.cpp:156-159`) | code i at bit `4·(i mod 8)` → low nibble = index 0 | low nibble = even index | +| Container | flat `data_/scales_` arrays, no header | self-framed `'SGF4'` stream, quadtree | MNN `symmetricQuan` flat arrays | +| Dual-mode / ternary | none | core feature | none | + +`sgfp4-arxiv-v2.txt:73-76` explicitly contrasts SGFP4 against NF4: "QLoRA's NF4 uses a 16-entry +non-uniform codebook… SGFP4 deliberately uses affine integer codes instead." GNUS-NEO-SWARM's +`fp4_codec` **is** the NF4 approach the spec deliberately rejects — even the nibble order is +inverted, so the packed bytes of the two formats are not interchangeable at any level. + +## MNN-Side Implementation Quality + +**Verdict: HIGH quality, genuinely complete for its locked v2-only scope.** + +All 4 phases are implemented in real source (not just planning docs), backed by real tests, and +Phase 4 was independently verified on live GPU hardware: + +| Phase | Status | Code evidence | +|-------|--------|----------------| +| 1 — Affine dual-mode CPU, uniform layouts | Complete (2026-08-24) | `include/MNN/SGFP4DequantUtils.hpp` (460 lines), `source/backend/cpu/CPUSGFP4Dequant.{cpp,hpp}`, registered in `CPUOPRegister.cpp`/`ShapeRegister.cpp` | +| 2 — Adaptive quadtree CPU, MIXED layout | Complete (2026-08-24) | `sgfp4_walk_quadtree` + MIXED branch (`SGFP4DequantUtils.hpp:188-222, 387-408`); encoder `tools/fp4/encode_sgfp4.py` (1085 lines) | +| 3 — Vulkan uniform layouts | Complete (2026-08-25) | `sgfp4_dequant.comp`, `VulkanSGFP4Dequant.{cpp,hpp}`, regenerated shader maps | +| 4 — Vulkan MIXED layout | Complete (2026-08-25) | `locateMixedLeaf` (`sgfp4_dequant.comp:114-180`); `04-VERIFICATION.md` reports a live run on an RTX 4070 Ti SUPER, 14 fixtures green | + +Fidelity to `sgfp4-arxiv-v2.txt` is close and section-by-section: the affine reconstruction +`w = S·c + bias` for both FP4_AFFINE and T158 ternary modes matches spec §3.2/§4.3 Eq.3/4 +exactly (including the reserved-symbol rule for ternary code `11`); the FP16 packed S+bias +header with 12-bit truncated-bias recovery matches §6.2 Eq.6 and uses the vendored +`half_float::half` rather than a hand-rolled implementation; the self-framed `'SGF4'` stream +(magic/version/16-byte-aligned record-offset table) matches §6.1; all 5 uniform layouts plus the +LAYOUT_MIXED quadtree pre-order DFS walk match §6.2 Table 3; every read is bounds-checked and +rejects malformed containers rather than reading out of bounds. The Vulkan shader is a +documented bit-for-bit port of the CPU walker, cross-checked line-by-line against the CPU split- +map reader in `04-VERIFICATION.md`. Test coverage is real: `test/op/SGFP4DequantTest.cpp` (866 +lines, two independent suites — one of which shares no code with the decoder, a deliberate +cross-check design decision), `test/op/SGFP4VulkanDequantTest.cpp` (CPU-vs-Vulkan parity within +rtol), and `tools/fp4/encode_sgfp4.py` (a real reference encoder with its own selftest). + +Minor red flags, none of which block correctness: + +- **Doc-hygiene:** `REQUIREMENTS.md:25-28` still shows Phase 2 requirements (SGV2-08…11) + unchecked and the traceability table lists them "Pending," even though the code fully + implements them — `STATE.md` acknowledges this is stale-doc debt, and a separate backfill + quick task confirmed via git history that Phase 2 was genuinely executed. +- **Doc-hygiene:** no `02-VERIFICATION.md` exists (Phases 1, 3, 4 each have one); Phase 2 + completion is evidenced only by SUMMARY files and commits. +- **Deferred-perf / unrelated blocker:** `test/op/FP4ModelTest.cpp` blocks a from-scratch + `run_test.out` build, but it is pre-existing dead code from an unrelated `milestone` + workstream, not something sgfp4-pivot introduced; verifiers worked around it with a temp stub. +- **Test-breadth:** the 14-fixture Vulkan sweep contains exactly one true LAYOUT_MIXED fixture + (`mixed_asymmetric`); the quadtree-on-GPU path is exercised but by a single tree shape. +- **Deferred-perf:** Vulkan decode does a full per-thread re-walk of the record/leaf structure + per output element (O(records × leaves) per element) rather than the spec's "one workgroup per + macroblock" shared-memory model — correctness-first by deliberate decision (SGV2-18), worth + revisiting only if throughput becomes a concern. + +Deliberately out of scope in MNN (locked decisions, not defects): the SGFP4 v1 fixed-payload +profile (v2-only), attestation / bit-exact conformance vectors (SuperGenius's job), and any +SuperGenius-side integration. + +## GNUS-NEO-SWARM Compatibility + +**Verdict: NOT compatible, and not even trying to be.** + +GNUS-NEO-SWARM's `fp4_codec` is a third, unrelated NF4 format — the exact non-uniform-codebook +approach the SGFP4 paper explicitly argues against (see table above). It is also self-flagged +orphaned dead code: `fp4_codec.hpp:8-13` documents "Flagged per Phase 4 D-13: this NF4-style +codec predates and does not match MNN_Ultra's E2M1 target format for `InputFormat::FP4_ULTRA`. +MNNInferenceEngine's reference to this class was confirmed orphaned and removed (Phase 4 plan +04-04). This class is a candidate for removal." Commit `8ee7fa4` ("fix(04-04): remove orphaned +fp4_codec member from MNNInferenceEngine") performed that removal; `test/core/test_fp4_codec.cpp` +(6 tests) still exists and tests the now-dead class in isolation, but nothing in live `src/` +calls into it. + +A grep for `sgfp4|affine|quadtree|t158` across GNUS-NEO-SWARM `src` returns nothing. +`mnn_inference_engine.cpp` has no SGFP4 awareness at all; for FP4 it only ever sets +`input_fmt = FP4_ULTRA` (`:266-267`) and hands off to the bridge, targeting MNN's E2M1 decode — +not SGFP4. + +The incompatibility is **architectural** — a different integration surface (model-weight sidecar +vs. input-tensor pass-through) — not version drift that would be fixed by pointing GNUS-NEO-SWARM +at a newer MNN commit. + +## SGProcessingManager Verification-Layer Implications + +The verification layer itself lives in SuperGenius/SGProcessingManager, which was not part of +the files read this session. On the GNUS-NEO-SWARM side, `SGProcessingBridge` +(`sg_processing_bridge.cpp`) is a thin client: it builds GNUS-schema JSON and submits a job +(`SubmitDirect` → `ProcessingManager::Create`/`Process`, `:308-380`); it performs no FP4 decode +and no verification itself. `TensorInterpreter` operates only on the output, which is already +dequantized to FLOAT32 by the time it returns (`tensor_interpreter.cpp:54-56`); it never sees a +raw FP4/SGFP4 container, and `InferViaSGProcessing` calls `Interpret(..., FLOAT32)` +unconditionally (`mnn_inference_engine.cpp:275`). + +**There is no silent-mis-verify risk today**, because no `InputFormat::SGFP4` (or `SGFP4_V2`) +enum exists anywhere in the format maps (`InputFormatToTypeString` / +`InputFormatToFormatString`, `sg_processing_bridge.cpp:40-95`, only know FLOAT32/16, INT8/16/32, +RGB(A)8, FP4_ULTRA). An SGFP4 job simply cannot be expressed today — it would be rejected at the +format gate rather than silently mis-decoded or mis-verified. + +Two items are surfaced honestly as unresolved rather than glossed over: + +1. **The FP4_ULTRA live-decode-vs-stub ambiguity (MEDIUM confidence; SuperGenius repo not read + this session).** MNN-side analysis (`SGFP4-PIVOT-ANALYSIS.md:126-142`) claims FP4_ULTRA + decode is wired live in SuperGenius (`processing_processor_mnn_tensor.cpp` calling + `MNN::dequant_fp4_packed_cpu`, commit `e1f28d7`) and flags a stale SuperGenius test asserting + the old unsupported-format behavior. GNUS-NEO-SWARM's own Phase 04 plan + (`04-02-PLAN.md:19-31`) describes FP4_ULTRA as "wire + stub" returning + `FORMAT_UNSUPPORTED`, from a point when MNN's E2M1 kernel was 39 commits diverged. These two + accounts are from different dates/branches and could not be reconciled without reading the + SuperGenius repo directly. +2. **The stale, self-contradicting `test_sg_connectivity.cpp` fp4_ultra assertion** (static + inference, not executed): `test_sg_connectivity.cpp:63-72` asserts the schema JSON contains + the lowercase literal `"fp4_ultra"`, but Phase 04-01 (`83a0e84`) changed the bridge so only + the uppercase `format` field carries `"FP4_ULTRA"` — this directly contradicts + `test_sgprocessing_pipeline.cpp:96`, which asserts the lowercase literal is absent. + +Both of these concern the E2M1/FP4_ULTRA path specifically. **SGFP4 is a separate concern from +FP4_ULTRA** — resolving the E2M1 live-vs-stub ambiguity does not address SGFP4 in any way, since +no SGFP4 code path exists on the GNUS-NEO-SWARM/SuperGenius side to resolve. + +## Integration Gaps + +The key clarification from "The Three FP4 Formats" above splits the gap list into two +conditional sets, depending on a scoping decision that has not yet been made. + +**If SGFP4 stays a model-weight concern (the likely correct reading):** + +- MNN submodule/build must include `OpType_SGFP4Dequant` and be built with + `MNN_SUPPORT_TRANSFORMER_FUSE` (the op and its tests are gated on this macro) — confirm the + MNN build GNUS-NEO-SWARM links against has this registered. +- Model export to SGFP4 sidecars belongs to `gnus-poc` per the existing GCS ROADMAP row + ("SGFP4 quantization export | gnus-poc | poc (Python)," `neoswarm/ROADMAP.md:161`); + `tools/fp4/encode_sgfp4.py` is the reference encoder to build on. +- `SGProcessingBridge` / `TensorInterpreter` need little to no change — output is still FLOAT32, + and the SGFP4 container lives with the model rather than the input tensor. +- The orphaned `fp4_codec.{hpp,cpp}` and its test should be deleted. + +**Additionally, if GCS/SuperGenius must speak SGFP4 as an input/wire format** (only if that +turns out to be a real requirement): + +- A new `InputFormat::SGFP4_V2` in SuperGenius `generated/InputFormat.hpp`. +- New cases in `InputFormatToTypeString` / `InputFormatToFormatString` + (`sg_processing_bridge.cpp:40-95`). +- A new SGFP4 validation branch in SGProcessingManager's `ProcessingManager::CheckProcessValidity` + and the `MNN_Tensor` processor (currently only FP4_ULTRA/E2M1). +- The arxiv §8 verifiable-execution/attestation story (bit-exact cross-device replay, ternary + integer-exact path) — this currently has **no anchor anywhere** in the codebase family and + would be net-new work in both MNN and SuperGenius if pursued. + +**Ownership-reconciliation gap:** GCS `neoswarm/ROADMAP.md:173` still assigns "SGFP4 GPU decode +shaders (Vulkan/MoltenVK)" to GNUS-NEO-SWARM — but MNN's sgfp4-pivot has already built exactly +that inside MNN (`sgfp4_dequant.comp`). That roadmap row is now duplicative/obsolete and needs +an explicit decision: does GNUS-NEO-SWARM consume MNN's SGFP4 Vulkan decode, or was that +ownership row superseded by MNN owning the decode outright? + +## Workstream Recommendation + +**CONDITIONAL — the answer depends on a scoping decision the developer needs to make, not on +anything ambiguous in the evidence.** + +- **If the goal is "run SGFP4-quantized models through MNN via the existing SGProcessing + path":** a dedicated workstream is **NOT** warranted. This is a handful of quick tasks: + confirm/bump the linked MNN build to include `OpType_SGFP4Dequant`; add SGFP4 export in + `gnus-poc`; write one end-to-end test running an SGFP4-weighted model; and a cleanup task + deleting the orphaned NF4 `fp4_codec` plus fixing the two stale FP4-related tests. The bridge + and interpreter are essentially unchanged, since decode stays internal to MNN and the output + is still FLOAT32. + +- **If the goal is the full cross-repo story** — GCS/SuperGenius speaking SGFP4 as a first-class + format and/or wiring the arxiv §8 verifiable-execution/attestation use case — **YES, a + dedicated workstream IS warranted.** It spans three repos (`gnus-poc` export, MNN submodule + integration, SuperGenius `InputFormat` + processor + verification), has genuine + sequencing/dependencies, and touches the reputation/consensus verification layer that is the + paper's core motivation. + +Non-binding phase sketch for the full-workstream case, for reference if that path is chosen: + +- **P1 — Reconcile & clean up:** delete orphaned NF4 `fp4_codec` + its test; fix stale + `test_sg_connectivity.cpp` and (cross-repo) the stale SuperGenius test; resolve the ROADMAP + ownership row (MNN owns SGFP4 decode vs. GNUS-NEO-SWARM shaders); pin the MNN submodule to a + commit that includes `OpType_SGFP4Dequant`. +- **P2 — SGFP4 model export + MNN graph consumption:** `gnus-poc` exports weights to SGFP4 + sidecars (building on `encode_sgfp4.py`); end-to-end inference of an SGFP4-weighted model + through MNN LLM / SGProcessing; output-parity check. +- **P3 (only if SGFP4-as-input is actually required):** SuperGenius `InputFormat::SGFP4_V2` + + bridge mapping + `ProcessingManager`/`MNN_Tensor` validation branch. +- **P4 (optional, arxiv §8):** verifiable-execution/attestation — bit-exact replay and + ternary integer-exact determinism class; net-new in both MNN and SuperGenius. + +**Siting recommendation (recommendation only — this task does NOT create it):** if and when a +new workstream is created later, it should live as a new top-level +`GeniusCognitiveSystem/.planning/workstreams/` entry, sibling to the existing `neo`/`neo-poc` +entries, pointing at the GNUS-NEO-SWARM submodule the same way `.planning/workstreams/neo/config.json` +does — submodule reference, `planning_root`, and `workstream_name` — **not** nested inside +`GNUS-NEO-SWARM/.planning/workstreams/`. This evaluation task does not create that workstream, +its `config.json`, or any ROADMAP entry; it only recommends the siting for future use. + +## Recommended Follow-Ups + +The items below are recommendations for future work. They are intentionally **not** performed as +part of this evaluation task. + +- [ ] Delete the orphaned `fp4_codec.{hpp,cpp}` and `test/core/test_fp4_codec.cpp` from + GNUS-NEO-SWARM (self-flagged dead code, consumer already removed in commit `8ee7fa4`). +- [ ] Fix the stale `test_sg_connectivity.cpp` fp4_ultra assertion, which contradicts the + already-updated `test_sgprocessing_pipeline.cpp`. +- [ ] Reconcile the `neoswarm/ROADMAP.md:173` row that still assigns SGFP4 GPU decode shaders to + GNUS-NEO-SWARM, now that MNN owns that decode. +- [ ] Resolve the FP4_ULTRA live-vs-stub ambiguity by reading the SuperGenius repo directly + (separate from and does not resolve the SGFP4 question). +- [ ] MNN-side doc hygiene: check off the stale Phase 2 REQUIREMENTS.md boxes and update the + traceability table; add the missing `02-VERIFICATION.md`. + +## Confidence and Caveats + +- **HIGH** on both codebases' source (both read in full this session), on MNN's phase-completion + evidence, on the three-formats finding, and on `fp4_codec` being orphaned dead code. +- **MEDIUM** on SuperGenius/SGProcessingManager internals — the SuperGenius repo itself was not + read this session; findings about it are secondhand via MNN-side analysis docs and + GNUS-NEO-SWARM plan documents. +- The FP4_ULTRA live-decode-vs-stub ambiguity is **unresolved** and would require reading the + SuperGenius repo to settle. +- The stale `test_sg_connectivity.cpp` failure is a **static inference**, not a result of + actually running the test. +- **No builds or tests were run** as part of this research or this evaluation. From fb81353396ff7616e906c65b2b9ff8cfd26b6b39 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Tue, 25 Aug 2026 18:43:01 -0400 Subject: [PATCH 22/49] docs(quick-260825-pgu): Evaluate MNN sgfp4-pivot FP4 implementation integration with GNUS-NEO-SWARM neo workspace FP4 codec and SGProcessingManager verification layer; assess MNN-side implementation quality; scope potential new GNUS-NEO-SWARM workstream for required updates --- .../260825-pgu-SUMMARY.md | 134 ++++++++++++++++++ .planning/workstreams/app/STATE.md | 10 +- 2 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 .planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-SUMMARY.md diff --git a/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-SUMMARY.md b/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-SUMMARY.md new file mode 100644 index 0000000..7ee1fa7 --- /dev/null +++ b/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-SUMMARY.md @@ -0,0 +1,134 @@ +--- +phase: quick-260825-pgu +plan: 01 +subsystem: docs +tags: [sgfp4, fp4, mnn, gnus-neo-swarm, sgprocessing, evaluation] + +# Dependency graph +requires: + - phase: quick-260825-pgu (research) + provides: 260825-pgu-RESEARCH.md (evidence base for this evaluation) +provides: + - Decision-oriented evaluation of MNN sgfp4-pivot vs GNUS-NEO-SWARM FP4/SGProcessing compatibility + - Conditional workstream recommendation with siting guidance +affects: [neo, neo-poc, gnus-poc, future sgfp4 workstream planning] + +# Tech tracking +tech-stack: + added: [] + patterns: [] + +key-files: + created: + - .planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-EVALUATION.md + modified: [] + +key-decisions: + - "Report-only deliverable per locked CONTEXT.md decision — no workstream, config.json, or ROADMAP.md scaffolded" + - "Workstream recommendation is explicitly conditional on a scoping decision (weight-compression-only vs full cross-repo wire-format story), not a blanket yes/no" + - "If/when a workstream is created later, it should be sited as a new top-level .planning/workstreams/ entry mirroring the neo config.json pattern, recommended only — not created here" + +patterns-established: [] + +requirements-completed: + - QUICK-260825-pgu-evaluate-mnn-sgfp4-pivot + +coverage: + - id: D1 + description: "EVALUATION.md synthesizes RESEARCH.md into a decision-oriented report with all required sections (three-formats framing, MNN quality verdict, GNUS-NEO-SWARM incompatibility, verification-layer implications, integration gaps, conditional workstream recommendation, follow-ups, confidence/caveats)" + requirement: "QUICK-260825-pgu-evaluate-mnn-sgfp4-pivot" + verification: + - kind: other + ref: "automated grep gate in 260825-pgu-PLAN.md — checks all required H2 headings, 'conditional' recommendation language, and 'workstreams/neo' siting reference are present" + status: pass + human_judgment: false + +# Metrics +duration: 15min +completed: 2026-08-25 +status: complete +--- + +# Quick Task 260825-pgu: SGFP4 Integration Evaluation Summary + +**Wrote a decision-oriented evaluation concluding MNN's sgfp4-pivot is complete/high-quality but architecturally incompatible with GNUS-NEO-SWARM's FP4 codec (orphaned NF4 dead code), with a conditional workstream recommendation gated on a scoping decision.** + +## Performance + +- **Duration:** ~15 min +- **Started:** 2026-08-25 (session start) +- **Completed:** 2026-08-25 +- **Tasks:** 1 completed +- **Files modified:** 1 (created) + +## Accomplishments +- Synthesized `260825-pgu-RESEARCH.md` into `260825-pgu-EVALUATION.md`, reorganized around + decisions rather than the research's Q1-Q5 order, per the plan's exact required section + structure (ten H2 sections). +- Led the technical body with the "three distinct, mutually-incompatible FP4 formats" + clarification and the "SGFP4 is weight-compression, not an input format" distinction that + reframes the rest of the evaluation. +- Delivered explicit verdicts: MNN sgfp4-pivot is HIGH quality and complete for its v2-only + scope (all 4 phases in real source with tests, Phase 4 GPU-verified); GNUS-NEO-SWARM is NOT + compatible and its own `fp4_codec` is orphaned NF4 dead code; SGProcessingManager verification + has no silent-mis-verify risk today because no SGFP4 format enum exists to express such a job. +- Split integration gaps into two conditional sets (weight-compression-only vs full + cross-repo wire-format story) and surfaced the ROADMAP ownership-reconciliation gap. +- Gave an explicit CONDITIONAL workstream recommendation (not a blanket yes/no), with the + top-level `.planning/workstreams/` siting stated as a recommendation only, mirroring the + `neo/config.json` pattern. +- Listed (without executing) concrete follow-ups: delete orphaned `fp4_codec`, fix stale tests, + reconcile ROADMAP ownership row, resolve FP4_ULTRA live-vs-stub ambiguity, MNN doc hygiene. + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Write the SGFP4 integration evaluation report** - `f1a6b46` (docs) + +**Plan metadata:** _(handled by orchestrator's docs commit, not included here)_ + +## Files Created/Modified +- `.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-EVALUATION.md` - Decision-oriented SGFP4 integration evaluation report + +## Decisions Made +None beyond what was already locked in CONTEXT.md — followed the plan's exact section structure +and writing constraints as specified. + +## Deviations from Plan + +None - plan executed exactly as written. All ten required sections were produced with the exact +headings the plan's automated verification greps for; the report leads with the three-formats +clarification as instructed; the workstream recommendation is explicitly conditional and cites +the `workstreams/neo` siting pattern; no workstream scaffolding, config.json, ROADMAP.md, or +code changes were created. + +## Issues Encountered +None. Top-level `.planning/STATE.md` in this repo is a stub pointing to the `neo` workstream's +state file rather than a full project STATE.md — this is expected repo structure (GCS aggregates +submodule workstreams) and did not affect this quick task, which per its constraints does not +touch STATE.md/ROADMAP.md directly (orchestrator handles those). + +## User Setup Required +None - no external service configuration required. + +## Next Phase Readiness +- The evaluation report is available for the developer to make the scoping decision described + in "Workstream Recommendation" (weight-compression-only quick tasks vs full cross-repo + workstream). +- No new workstream was created; if the developer chooses the full-workstream path, the report's + non-binding P1-P4 phase sketch and siting recommendation (new top-level + `.planning/workstreams/` entry mirroring `neo/config.json`) are ready to hand to + `/gsd-new-milestone` or equivalent workstream-creation tooling. +- Recommended follow-up cleanup items (orphaned `fp4_codec`, stale tests, ROADMAP ownership row) + remain unexecuted and available as candidate quick tasks regardless of which scoping path is + chosen. + +--- +*Phase: quick-260825-pgu* +*Completed: 2026-08-25* + +## Self-Check: PASSED + +- FOUND: `.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-EVALUATION.md` +- FOUND: commit `f1a6b46` diff --git a/.planning/workstreams/app/STATE.md b/.planning/workstreams/app/STATE.md index f381f84..4e7d689 100644 --- a/.planning/workstreams/app/STATE.md +++ b/.planning/workstreams/app/STATE.md @@ -5,7 +5,7 @@ milestone_name: GCS Chat status: executing stopped_at: Phase 1 context gathered last_updated: "2026-08-16T02:39:53.797Z" -last_activity: 2026-08-16 -- Phase 01 planning complete +last_activity: 2026-08-25 - Completed quick task 260825-pgu: Evaluate MNN sgfp4-pivot FP4 implementation integration with GNUS-NEO-SWARM neo workspace FP4 codec and SGProcessingManager verification layer progress: total_phases: 7 completed_phases: 0 @@ -28,7 +28,7 @@ See: .planning/workstreams/app/PROJECT.md (updated 2026-08-15) Phase: 1 of 7 (Foundation) Plan: 0 of TBD in current phase Status: Ready to execute -Last activity: 2026-08-16 -- Phase 01 planning complete +Last activity: 2026-08-25 - Completed quick task 260825-pgu: Evaluate MNN sgfp4-pivot FP4 implementation integration with GNUS-NEO-SWARM neo workspace FP4 codec and SGProcessingManager verification layer Progress: [░░░░░░░░░░] 0% @@ -75,6 +75,12 @@ None yet. - Phase 1 depends on GlobalDB CRDT integration from GNUS-NEO-SWARM Phase 3 — verify availability before planning. +### Quick Tasks Completed + +| # | Description | Date | Commit | Directory | +|---|-------------|------|--------|-----------| +| 260825-pgu | Evaluate MNN sgfp4-pivot FP4 implementation integration with GNUS-NEO-SWARM neo workspace FP4 codec and SGProcessingManager verification layer; assess MNN-side implementation quality; scope potential new GNUS-NEO-SWARM workstream for required updates | 2026-08-25 | f1a6b46 | [260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat](../../quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/) | + ## Deferred Items Items acknowledged and carried forward from previous milestone close: From 698abd7e9641272565a1cc7594312ae3e4f9d4e1 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Wed, 2 Sep 2026 15:24:38 -0400 Subject: [PATCH 23/49] docs(quick-260825-pgu): re-evaluate MNN sgfp4-pivot after phases 5-12 (converter integration) MNN's sgfp4-pivot grew from 4 to 12 phases since the original evaluation and now ships mnnconvert --sgfp4, closing the core "nothing produces a real SGFP4 .mnn" gap this report identified. Appends an addendum: the converter path is real and verified against the classic Interpreter/Session API, but corrects the original phases 1-4 verdict (CPU decoder was broken for real multi-tile weights, now fixed), and flags two new open risks (externalized- weight loading, unproven accuracy). --- .../260825-pgu-EVALUATION.md | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-EVALUATION.md b/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-EVALUATION.md index 6734613..998e0ba 100644 --- a/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-EVALUATION.md +++ b/.planning/quick/260825-pgu-evaluate-mnn-sgfp4-pivot-fp4-implementat/260825-pgu-EVALUATION.md @@ -275,3 +275,40 @@ part of this evaluation task. - The stale `test_sg_connectivity.cpp` failure is a **static inference**, not a result of actually running the test. - **No builds or tests were run** as part of this research or this evaluation. + +## Addendum (2026-08-26): MNN `sgfp4-pivot` re-evaluated after phases 5-12 + +Between this report and 2026-08-26, MNN's `sgfp4-pivot` workstream grew from 4 phases to 12 +(two milestones: v2.0 "injection tool", v3.0 "converter integration") and was archived complete. +Re-evaluated via a fresh research pass; findings that update or correct the above: + +- **The core gap this report identified is now closed.** `mnnconvert --sgfp4` is real: a + self-contained 848-line C++ port of gnus-poc's encoder (`tools/fp4/sgfp4_encode.cpp`) now runs + inside MNN's converter, does its own scale/bias fitting and quadtree layout decisions, and + rewrites the graph to `OpType_SGFP4Dequant`. The output is verified to load and run through the + **classic `Interpreter`/`Session` API** (the one SGProcessingManager actually uses) on both CPU + and Vulkan — the exact thing this report flagged as unverified. A separate `sgfp4_inject` tool + (v2.0) also exists, consuming pre-encoded gnus-poc `.sgfp4` containers directly. +- **Correction to this report's "high quality, genuinely complete" verdict on phases 1-4:** Phase + 12 discovered the CPU decoder was actually broken for real multi-tile/MIXED weights — it + produced garbage output on any shape beyond the single-superblock cases the original tests + happened to cover (wrong stream-order convention + a split-map coordinate/child-order bug). Both + fixed and cross-validated in commit `54bbeaf8`. The original verdict was accurate only for the + narrow shapes actually tested, not demonstrated as broadly correct as stated. +- **Two new risks, not yet resolved:** + 1. Converter-produced SGFP4 models that externalize weights (large models spilling to a + `.weight` sidecar) set `external={offset,size}` but not `op->externalPath` — the same + auto-injection gap this report already flagged for `OpType_SGFP4Dequant` is still open on the + converter's externalization path specifically. Verified working only for inline (small) + models; likely broken for large ones. + 2. The E2E validation gate (`tools/fp4/e2e_validation.ps1`) proves the pipeline runs without + crashing (synthetic input, tolerances locked at 2x a one-time-measured deviation of ~475-950% + relative error) — it is a liveness/regression gate, not an accuracy proof. No real-data + accuracy or classification-correctness check exists anywhere in this workstream. +- **gnus-poc relationship clarified:** `mnnconvert --sgfp4` does not call gnus-poc at runtime, but + gnus-poc's `fp4_exporter.py --adaptive` remains the reference oracle the C++ port was validated + against and is deliberately kept byte-parity with — not redundant, still load-bearing as the + spec-of-record and as the required input to the separate `sgfp4_inject` path. +- **Still out of scope / unbuilt:** SuperGenius/SGProcessingManager integration (explicitly locked + out of this workstream), real-data accuracy validation, and MatMul-weight coverage (only + conv-family ops are quantized; light-tier tensors under 4096 elements stay FP32). From cc6280f55a1aebb8893a7c1ec8094e2915547f27 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Thu, 3 Sep 2026 14:24:50 -0400 Subject: [PATCH 24/49] fix(app): align gcs_storage/gcs_core with current GeniusSDK API and C++17 Port the corrected implementation from the GNUS-NEO-SWARM twin (src/storage/gcs_global_db.cpp): - GeniusSDK.hpp/GeniusSDKGetNode() no longer exist in GeniusSDK dev_cognitive; use the installed C header and the opaque GeniusSDKGetPubSub() handle with a non-owning shared_ptr alias. - Add the missing include for the AsioSchedulerBackend construction. - Replace the designated initializer in gcs_core.cpp (C7555 under /std:c++17) with ordered aggregate init (single-member Config). Bump GNUS-NEO-SWARM submodule to d3f941c (Vulkan header include fix). --- GNUS-NEO-SWARM | 2 +- src/lib/gcs_core.cpp | 3 +-- src/lib/gcs_storage/gcs_global_db.cpp | 30 ++++++++++++++------------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/GNUS-NEO-SWARM b/GNUS-NEO-SWARM index bc2d52d..6af5fd1 160000 --- a/GNUS-NEO-SWARM +++ b/GNUS-NEO-SWARM @@ -1 +1 @@ -Subproject commit bc2d52d96f1b2c99c11dcf885cbd49a247e0f22c +Subproject commit 6af5fd1d37a43f01170af692097cf3b4b7549eb6 diff --git a/src/lib/gcs_core.cpp b/src/lib/gcs_core.cpp index 5bdd04d..3117929 100644 --- a/src/lib/gcs_core.cpp +++ b/src/lib/gcs_core.cpp @@ -15,8 +15,7 @@ namespace gcs { CoreSession::CoreSession(Config config) : m_config(std::move(config)), m_db(std::make_unique( - sgns::neoswarm::storage::GcsGlobalDb::Config{ - .m_dbPath = m_config.m_dbPath})) {} + sgns::neoswarm::storage::GcsGlobalDb::Config{m_config.m_dbPath})) {} CoreSession::~CoreSession() { if (IsRunning()) { diff --git a/src/lib/gcs_storage/gcs_global_db.cpp b/src/lib/gcs_storage/gcs_global_db.cpp index 15a9744..f707c6a 100644 --- a/src/lib/gcs_storage/gcs_global_db.cpp +++ b/src/lib/gcs_storage/gcs_global_db.cpp @@ -12,9 +12,10 @@ #include #include +#include #include -#include "GeniusSDK.hpp" +#include "GeniusSDK.h" #include "crdt/crdt_options.hpp" #include "crdt/globaldb/globaldb.hpp" @@ -74,23 +75,24 @@ GcsGlobalDb::~GcsGlobalDb() { Shutdown(); } outcome::result GcsGlobalDb::Initialize() { // Step 2: PubSub acquisition (D-15, D-16, D-16a) — pull from the in-process - // GeniusSDK. - auto node = GeniusSDKGetNode(); - if (!node) { + // GeniusSDK. GeniusSDKGetPubSub() returns an opaque, non-owning handle to + // the SDK's internal GossipPubSub (ownership stays with GeniusSDK's node + // instance — D-20 ordering guarantees the SDK, and therefore this handle, + // outlives this component). + void *pubsubHandle = GeniusSDKGetPubSub(); + if (!pubsubHandle) { m_logger->error( - "GcsGlobalDb::Initialize — GeniusSDKGetNode() returned nullptr; " - "GeniusSDK init chain has not run (D-20 ordering: SDK before " - "GlobalDB)"); + "GcsGlobalDb::Initialize — GeniusSDKGetPubSub() returned nullptr; " + "GeniusSDK init chain has not run yet, or pubsub is not started " + "(D-20 ordering: SDK before GlobalDB)"); return outcome::failure(Error::SdkNotInitialized); } - auto pubsub = node->GetPubSub(); - if (!pubsub) { - m_logger->error( - "GcsGlobalDb::Initialize — GeniusNode::GetPubSub() returned nullptr; " - "SDK is up but pubsub is not started"); - return outcome::failure(Error::SdkNotInitialized); - } + // Non-owning alias: no-op deleter, since GeniusSDK's node instance retains + // true ownership of the underlying GossipPubSub for the process lifetime. + auto pubsub = std::shared_ptr( + static_cast(pubsubHandle), + [](sgns::ipfs_pubsub::GossipPubSub *) {}); return Initialize(std::move(pubsub)); } From c4176cd645cc216875896f3e8819fbd357f21503 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Thu, 3 Sep 2026 14:25:41 -0400 Subject: [PATCH 25/49] chore: point GNUS-NEO-SWARM at develop tip (d3f941c) The previous commit recorded the phase-13 branch tip (6af5fd1) because the submodule working tree was checked out there. For the dev_phase04 -> develop PR the pointer must be the submodule develop tip so phase-13 content lands via its own submodule PR, not here. --- GNUS-NEO-SWARM | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GNUS-NEO-SWARM b/GNUS-NEO-SWARM index 6af5fd1..d3f941c 160000 --- a/GNUS-NEO-SWARM +++ b/GNUS-NEO-SWARM @@ -1 +1 @@ -Subproject commit 6af5fd1d37a43f01170af692097cf3b4b7549eb6 +Subproject commit d3f941c7195e329dd4e3a4f769e0ab8c63e01109 From b3ef13155c1ced887b3cd09d7a7cf3f03d5a66ac Mon Sep 17 00:00:00 2001 From: itsafuu Date: Fri, 18 Sep 2026 18:15:31 -0400 Subject: [PATCH 26/49] Vulkan discovery --- cmake/CommonBuildParameters.cmake | 140 +++++++++++++++++++++--------- 1 file changed, 101 insertions(+), 39 deletions(-) diff --git a/cmake/CommonBuildParameters.cmake b/cmake/CommonBuildParameters.cmake index cef22b3..b2810a3 100644 --- a/cmake/CommonBuildParameters.cmake +++ b/cmake/CommonBuildParameters.cmake @@ -166,6 +166,100 @@ set(MNN_DIR "${THIRDPARTY_BUILD_DIR}/MNN/lib/cmake/MNN") find_package(MNN CONFIG REQUIRED) include_directories(${MNN_INCLUDE_DIR}) +# -------------------------------------------------------- +# Vulkan / VulkanHeaders (GPU acceleration — needed by MNN/SGProcessingManager +# via GNUS-NEO-SWARM). MUST be established before find_package(vk-bootstrap) +# below: the rebuilt vk-bootstrap package config does +# find_package(VulkanHeaders CONFIG) with a find_package(Vulkan) fallback and +# fatals when neither resolves, and this machine has no system Vulkan SDK. +# Ported from the updated discovery in +# GeniusNetwork/SuperGenius/build/CommonBuildParameters.cmake. +# The Vulkan::Vulkan target created here is still visible to both +# add_subdirectory() subtrees at the bottom of this file (sibling +# add_subdirectory scopes don't share targets with each other, only with +# their common parent — which is this scope). +if(APPLE) + if(IOS) + # Settings specifically for iOS + set(Vulkan_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/moltenvk/build/include") + set(Vulkan_LIBRARY "${THIRDPARTY_BUILD_DIR}/moltenvk/build/lib/MoltenVK.xcframework") + else() + # Settings for macOS + set(Vulkan_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/moltenvk/build/include") + set(Vulkan_LIBRARY "${THIRDPARTY_BUILD_DIR}/moltenvk/build/lib/MoltenVK.xcframework") + endif() +endif() + +set(VulkanHeaders_DIR "${THIRDPARTY_BUILD_DIR}/Vulkan-Headers/share/cmake/VulkanHeaders" CACHE PATH "Path to Vulkan-Headers install folder") +find_package(VulkanHeaders CONFIG REQUIRED) +find_package(Vulkan) + +if(NOT TARGET Vulkan::Vulkan) + set(Vulkan_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/Vulkan-Headers/include") + if(NOT DEFINED ENV{VULKAN_SDK}) + set(ENV{VULKAN_SDK} "${THIRDPARTY_BUILD_DIR}/Vulkan-Loader") + endif() + + find_package(Vulkan REQUIRED) +endif() + +# Override Vulkan::Vulkan to use our vendored Vulkan-Headers on all platforms. +# vk-bootstrap was built against our headers (v1.4); mixing with system/NDK +# headers (v1.3 or other versions) causes unknown-type errors in +# VkBootstrapDispatch.h and VkBootstrapFeatureChain.h. +set_target_properties(Vulkan::Vulkan PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${THIRDPARTY_BUILD_DIR}/Vulkan-Headers/include" +) + +# On macOS, libMoltenVK.a contains Objective-C code that calls Metal. +# The ObjC runtime (-lobjc) and Metal frameworks must be linked by +# every consumer of Vulkan::Vulkan or the linker fails with undefined +# _objc_msgSend / _objc_retain / _objc_release etc. +# AppKit does not exist on iOS (ld: framework 'AppKit' not found); MoltenVK +# uses UIKit there, mirroring the gating in SGProcessors. +if(APPLE) + target_link_libraries(Vulkan::Vulkan INTERFACE + "-framework Metal" + "-framework IOSurface" + "-framework QuartzCore" + "-framework Foundation" + "-framework CoreFoundation" + "-framework CoreGraphics" + "-framework IOKit" + ) + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + target_link_libraries(Vulkan::Vulkan INTERFACE "-framework AppKit") + else() + target_link_libraries(Vulkan::Vulkan INTERFACE "-framework UIKit") + endif() +endif() + +# Resolve the Vulkan runtime DLL that matches the loader we just linked. +# The thirdparty Vulkan-Loader installs vulkan-1.dll (runtime) alongside +# vulkan-1.lib (import lib, found above). Every exe that links it — directly +# or via SGProcessors' MNN::MNN + Vulkan::Vulkan PUBLIC deps — needs the DLL +# next to the exe or the Windows loader kills the process with 0xc0000135 +# before main() runs. CI runners have no system Vulkan runtime, so the +# vendored one must be deployed next to test/app executables. +if(WIN32) + find_file(VULKAN_RUNTIME_DLL NAMES vulkan-1.dll + PATHS "${THIRDPARTY_BUILD_DIR}/Vulkan-Loader/bin" + "${THIRDPARTY_BUILD_DIR}/Vulkan-Loader/lib" + NO_DEFAULT_PATH) + + if(NOT VULKAN_RUNTIME_DLL) + # Only fatal when we actually link the thirdparty loader; a system + # Vulkan SDK brings its own runtime on PATH. + string(FIND "${Vulkan_LIBRARY}" "${THIRDPARTY_BUILD_DIR}" _GCS_VK_LOADER_IS_VENDORED) + if(_GCS_VK_LOADER_IS_VENDORED EQUAL 0) + message(FATAL_ERROR "vulkan-1.dll not found in " + "${THIRDPARTY_BUILD_DIR}/Vulkan-Loader (searched bin/ and lib/). " + "Executables link ${Vulkan_LIBRARY} and will fail to start with " + "0xc0000135 without the matching runtime DLL.") + endif() + endif() +endif() + # vk-bootstrap set(vk-bootstrap_DIR "${THIRDPARTY_BUILD_DIR}/vk-bootstrap/lib/cmake/vk-bootstrap") find_package(vk-bootstrap CONFIG REQUIRED) @@ -359,45 +453,13 @@ set(LLVM_DIR "${ZKLLVM_BUILD_DIR}/zkLLVM/lib/cmake/llvm") find_package(LLVM CONFIG REQUIRED) # -------------------------------------------------------- -# Vulkan (GPU acceleration — needed by MNN/SGProcessingManager via -# GNUS-NEO-SWARM). Established here, before either add_subdirectory() call -# below, so the resulting Vulkan::Vulkan target is visible to both the -# GNUS-NEO-SWARM subtree and the GCS-level src/ subtree (sibling -# add_subdirectory scopes don't share targets with each other, only with -# their common parent). Ported from -# GeniusNetwork/GeniusSDK/cmake/CommonBuildParameters.cmake. -if(APPLE) - if(IOS) - # Settings specifically for iOS - set(Vulkan_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/moltenvk/build/include") - set(Vulkan_LIBRARY "${THIRDPARTY_BUILD_DIR}/moltenvk/build/lib/MoltenVK.xcframework") - else() - # Settings for macOS - set(Vulkan_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/moltenvk/build/include") - set(Vulkan_LIBRARY "${THIRDPARTY_BUILD_DIR}/moltenvk/build/lib/MoltenVK.xcframework") - endif() -endif() - -set(VulkanHeaders_DIR "${THIRDPARTY_BUILD_DIR}/Vulkan-Headers/share/cmake/VulkanHeaders" CACHE PATH "Path to Vulkan-Headers install folder") -find_package(VulkanHeaders CONFIG REQUIRED) -find_package(Vulkan) - -if(NOT TARGET Vulkan::Vulkan) - set(Vulkan_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/Vulkan-Headers/include") - if(NOT DEFINED ENV{VULKAN_SDK}) - set(ENV{VULKAN_SDK} "${THIRDPARTY_BUILD_DIR}/Vulkan-Loader") - endif() - - find_package(Vulkan REQUIRED) -endif() - -# Force Vulkan::Vulkan to use the vendored Vulkan-Headers on every platform, -# even when find_package(Vulkan) resolves against a system-installed SDK. -# vk-bootstrap/MNN here are built against the vendored headers, so mixing in -# a different system header version causes unknown-type errors downstream. -set_target_properties(Vulkan::Vulkan PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${THIRDPARTY_BUILD_DIR}/Vulkan-Headers/include" -) +# NOTE: Vulkan discovery (VulkanHeaders + Vulkan::Vulkan + runtime DLL +# resolution) now lives above, before find_package(vk-bootstrap) — the +# rebuilt vk-bootstrap config requires Vulkan::Headers at its own +# find_package() time and fatals when it is only configured here. The +# Vulkan::Vulkan target is still created before either add_subdirectory() +# call below, so it remains visible to both the GNUS-NEO-SWARM subtree and +# the GCS-level src/ subtree. # -------------------------------------------------------- # SuperGenius (sibling repo under GeniusNetwork). Must be found BEFORE From 4a7982b757b0bec22f4edac409bed4b8786694bc Mon Sep 17 00:00:00 2001 From: itsafuu Date: Fri, 18 Sep 2026 18:15:48 -0400 Subject: [PATCH 27/49] Submodule bump --- GNUS-NEO-SWARM | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GNUS-NEO-SWARM b/GNUS-NEO-SWARM index d3f941c..6af5fd1 160000 --- a/GNUS-NEO-SWARM +++ b/GNUS-NEO-SWARM @@ -1 +1 @@ -Subproject commit d3f941c7195e329dd4e3a4f769e0ab8c63e01109 +Subproject commit 6af5fd1d37a43f01170af692097cf3b4b7549eb6 From 341584b34aae849050d54ae25448168d544f2d29 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Mon, 21 Sep 2026 15:36:37 -0400 Subject: [PATCH 28/49] =?UTF-8?q?fix(submodule):=20restore=20scaffold=20pi?= =?UTF-8?q?n=20to=20pushed=20commit=206016e3f=20=E2=80=94=20develop's=203d?= =?UTF-8?q?9ebab=20pinned=207760ee2=20which=20was=20never=20pushed=20to=20?= =?UTF-8?q?the=20scaffold=20remote?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/scaffold | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/scaffold b/src/app/scaffold index 7760ee2..6016e3f 160000 --- a/src/app/scaffold +++ b/src/app/scaffold @@ -1 +1 @@ -Subproject commit 7760ee2a92ded18ea5efe8b8ade494d0f1ceaad6 +Subproject commit 6016e3f484b62e3dcccd908f35243e7a444a252b From 62e6326db47719c85b4e495b25acf6ed19b4fd60 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Mon, 21 Sep 2026 16:03:45 -0400 Subject: [PATCH 29/49] fix(storage): repair auto-merge corruption in gcs_global_db.cpp Initialize The develop merge (b63b61e) textually combined our GeniusSDKGetPubSub() C-API acquisition with develop's node->GetGraphsyncNetwork() body, leaving 'node' undeclared and calling a nonexistent 1-arg Initialize overload. Restore develop's 0c68fb2 implementation verbatim: GeniusSDKGetNode() -> node->GetPubSub() / node->GetGraphsyncNetwork(), and the GeniusSDK.hpp include it requires (verified present in the installed local GeniusSDK package the build resolves). --- src/lib/gcs_storage/gcs_global_db.cpp | 29 +++++++++++++-------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/lib/gcs_storage/gcs_global_db.cpp b/src/lib/gcs_storage/gcs_global_db.cpp index 48643f5..c891f7b 100644 --- a/src/lib/gcs_storage/gcs_global_db.cpp +++ b/src/lib/gcs_storage/gcs_global_db.cpp @@ -19,7 +19,7 @@ #include #include -#include "GeniusSDK.h" +#include "GeniusSDK.hpp" #include "crdt/crdt_options.hpp" #include "crdt/globaldb/globaldb.hpp" @@ -79,24 +79,23 @@ GcsGlobalDb::~GcsGlobalDb() { Shutdown(); } outcome::result GcsGlobalDb::Initialize() { // Step 2: PubSub acquisition (D-15, D-16, D-16a) — pull from the in-process - // GeniusSDK. GeniusSDKGetPubSub() returns an opaque, non-owning handle to - // the SDK's internal GossipPubSub (ownership stays with GeniusSDK's node - // instance — D-20 ordering guarantees the SDK, and therefore this handle, - // outlives this component). - void *pubsubHandle = GeniusSDKGetPubSub(); - if (!pubsubHandle) { + // GeniusSDK. + auto node = GeniusSDKGetNode(); + if (!node) { m_logger->error( - "GcsGlobalDb::Initialize — GeniusSDKGetPubSub() returned nullptr; " - "GeniusSDK init chain has not run yet, or pubsub is not started " - "(D-20 ordering: SDK before GlobalDB)"); + "GcsGlobalDb::Initialize — GeniusSDKGetNode() returned nullptr; " + "GeniusSDK init chain has not run (D-20 ordering: SDK before " + "GlobalDB)"); return outcome::failure(Error::SdkNotInitialized); } - // Non-owning alias: no-op deleter, since GeniusSDK's node instance retains - // true ownership of the underlying GossipPubSub for the process lifetime. - auto pubsub = std::shared_ptr( - static_cast(pubsubHandle), - [](sgns::ipfs_pubsub::GossipPubSub *) {}); + auto pubsub = node->GetPubSub(); + if (!pubsub) { + m_logger->error( + "GcsGlobalDb::Initialize — GeniusNode::GetPubSub() returned nullptr; " + "SDK is up but pubsub is not started"); + return outcome::failure(Error::SdkNotInitialized); + } // Borrow the node's graphsync Network (D-17, amended 2026-08-27): a libp2p // host keeps ONE protocol-handler slot per protocol, so constructing a new From 3cc81b9d4145b09c20bbcae45865a7caf6b38820 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Mon, 21 Sep 2026 16:24:37 -0400 Subject: [PATCH 30/49] ci: manual dispatch controls, AlmaLinux 8 migration, tag-release race fix - workflow_dispatch: add per-platform build toggles (build_- inputs + exit 78 skip step) and thirdparty/supergenius/ geniussdk dep-tag overrides that default to the release tag, matching the GeniusSDK/SuperGenius workflows - Migrate Linux container jobs off EOL Debian Bullseye to ghcr.io/geniusventures/almalinux-8:latest; split Configure Linux host into dnf/apt branches and pin clang via -DCMAKE_{C,CXX}_COMPILER=clang{,++} (AlmaLinux /usr/bin/c++ is a real gcc-owned file, so the update-alternatives takeover fails silently) - Create release tag now also runs on manual tag dispatches and treats an existing release as reusable for the shared tag, with a create-race retry for sibling matrix jobs --- .github/workflows/cmake.yml | 165 +++++++++++++++++++++++++++++++----- 1 file changed, 144 insertions(+), 21 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index c2a2c42..a044094 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -24,9 +24,79 @@ on: workflow_dispatch: inputs: tag: - description: "Release tag" + description: "Release tag (release to create/upload to; dep tags default to it)" required: false type: string + thirdparty_tag: + description: "Tag for thirdparty dependency (defaults to main tag if not specified)" + required: false + type: string + supergenius_tag: + description: "Tag for SuperGenius dependency (defaults to main tag if not specified)" + required: false + type: string + geniussdk_tag: + description: "Tag for GeniusSDK dependency (defaults to main tag if not specified)" + required: false + type: string + # Names must be build_-_: the skip step looks + # them up dynamically instead of enumerating every combination. + build_Android-arm64-v8a_Release: + description: "Build Android arm64-v8a Release" + default: true + type: boolean + build_Android-arm64-v8a_Debug: + description: "Build Android arm64-v8a Debug" + default: true + type: boolean + build_Android-armeabi-v7a_Release: + description: "Build Android armeabi-v7a Release" + default: true + type: boolean + build_Android-armeabi-v7a_Debug: + description: "Build Android armeabi-v7a Debug" + default: true + type: boolean + build_iOS_Release: + description: "Build iOS Release" + default: true + type: boolean + build_iOS_Debug: + description: "Build iOS Debug" + default: true + type: boolean + build_OSX_Release: + description: "Build OSX Release" + default: true + type: boolean + build_OSX_Debug: + description: "Build OSX Debug" + default: true + type: boolean + build_Linux-x86_64_Release: + description: "Build Linux x86_64 Release" + default: true + type: boolean + build_Linux-x86_64_Debug: + description: "Build Linux x86_64 Debug" + default: true + type: boolean + build_Linux-aarch64_Release: + description: "Build Linux aarch64 Release" + default: true + type: boolean + build_Linux-aarch64_Debug: + description: "Build Linux aarch64 Debug" + default: true + type: boolean + build_Windows_Release: + description: "Build Windows Release" + default: true + type: boolean + build_Windows_Debug: + description: "Build Windows Debug" + default: true + type: boolean permissions: contents: write packages: read @@ -248,22 +318,22 @@ jobs: runner_key: linux_x64 abi: "x86_64" build-type: "Release" - container: ghcr.io/geniusventures/debian-bullseye:latest + container: ghcr.io/geniusventures/almalinux-8:latest - target: Linux runner_key: linux_x64 abi: "x86_64" build-type: "Debug" - container: ghcr.io/geniusventures/debian-bullseye:latest + container: ghcr.io/geniusventures/almalinux-8:latest - target: Linux runner_key: linux_arm abi: "aarch64" build-type: "Release" - container: ghcr.io/geniusventures/debian-bullseye:latest + container: ghcr.io/geniusventures/almalinux-8:latest - target: Linux runner_key: linux_arm abi: "aarch64" build-type: "Debug" - container: ghcr.io/geniusventures/debian-bullseye:latest + container: ghcr.io/geniusventures/almalinux-8:latest - target: Windows runner_key: windows - target: OSX @@ -292,6 +362,13 @@ jobs: - target: Linux abi: "" steps: + # Shell-agnostic body so this works on both bash and pwsh runners. + - name: Skip build disabled by workflow_dispatch input + if: ${{ github.event_name == 'workflow_dispatch' && !inputs[format('build_{0}{1}_{2}', matrix.target, matrix.abi && format('-{0}', matrix.abi) || '', matrix.build-type)] }} + run: | + echo "Skipping build for ${{ matrix.target }}${{ matrix.abi && format(' {0}', matrix.abi) || '' }} ${{ matrix.build-type }}" + exit 78 + - name: Configure bash on Windows (use Git Bash, not WSL) if: ${{ runner.environment == 'self-hosted' && matrix.target == 'Windows' }} run: | @@ -379,6 +456,7 @@ jobs: env: GITHUB_EVENT_INPUTS_TAG: ${{ github.event.inputs.tag }} GITHUB_EVENT_PULL_REQUEST_HEAD_REF: ${{ github.event.pull_request.head.ref }} + THIRDPARTY_TAG: ${{ github.event.inputs.thirdparty_tag || github.event.inputs.tag }} run: | # Determine current branch or tag if [ "${{ github.event_name }}" == "workflow_dispatch" ] && [ "${GITHUB_EVENT_INPUTS_TAG}" ]; then @@ -398,8 +476,9 @@ jobs: # Determine thirdparty branch to use if [ "$IS_TAG" == "true" ]; then - # For tags, use the exact tag name for thirdparty - thirdparty_branch="$CURRENT_BRANCH" + # For tags, use the thirdparty_tag input, falling back to the + # release tag itself (GeniusSDK/SuperGenius tag-build parity). + thirdparty_branch="${THIRDPARTY_TAG}" elif [ "$CURRENT_BRANCH" == "main" ]; then thirdparty_branch="main" else @@ -450,11 +529,14 @@ jobs: - name: Download SuperGenius shell: bash + env: + SUPERGENIUS_TAG: ${{ github.event.inputs.supergenius_tag || github.event.inputs.tag }} run: | # Use same branch determination logic for SuperGenius if [ "$IS_TAG" == "true" ]; then - # For tags, use the exact tag name for SuperGenius - sgns_branch="$CURRENT_BRANCH" + # For tags, use the supergenius_tag input, falling back to the + # release tag itself. + sgns_branch="${SUPERGENIUS_TAG}" elif [ "$CURRENT_BRANCH" == "main" ]; then sgns_branch="main" else @@ -493,11 +575,14 @@ jobs: # release (the GeniusSDK repo builds itself instead). - name: Download GeniusSDK shell: bash + env: + GENIUSSDK_TAG: ${{ github.event.inputs.geniussdk_tag || github.event.inputs.tag }} run: | # Use same branch determination logic for GeniusSDK if [ "$IS_TAG" == "true" ]; then - # For tags, use the exact tag name for GeniusSDK - sdk_branch="$CURRENT_BRANCH" + # For tags, use the geniussdk_tag input, falling back to the + # release tag itself. + sdk_branch="${GENIUSSDK_TAG}" elif [ "$CURRENT_BRANCH" == "main" ]; then sdk_branch="main" else @@ -540,9 +625,22 @@ jobs: sudo update-alternatives --set cc $(which clang) sudo update-alternatives --set c++ $(which clang++) - # dbus + gnome-keyring: the Linux ctest step runs under - # dbus-run-session with an unlocked keyring (SuperGenius precedent). - sudo apt install ccache ninja-build libvulkan-dev libzstd-dev libsecret-1-dev dbus gnome-keyring -y + # Native Linux builds run in the AlmaLinux 8 CI image (glibc 2.28 + # floor, replacing the EOL Debian Bullseye container — same + # migration as SuperGenius/GeniusSDK). The image ships clang, cmake, + # ninja, Vulkan headers, and libsecret, so the dnf branch is a + # guarded safety net that avoids repo traffic when the image already + # provides everything; the Ubuntu hosts used by the Android builds + # (which run without a container) still need apt. dbus + + # gnome-keyring stay in both branches: the Linux ctest step runs + # under dbus-run-session with an unlocked keyring. + if command -v dnf >/dev/null 2>&1; then + if ! rpm -q ccache ninja-build vulkan-loader-devel libsecret-devel libzstd-devel dbus gnome-keyring >/dev/null 2>&1; then + sudo dnf install -y ccache ninja-build vulkan-loader-devel libsecret-devel libzstd-devel dbus gnome-keyring + fi + else + sudo apt install ccache ninja-build libvulkan-dev libzstd-dev libsecret-1-dev dbus gnome-keyring -y + fi echo "CMAKE_GENERATOR=Ninja" >> $GITHUB_ENV - name: Configure Windows host @@ -623,7 +721,14 @@ jobs: - name: Configure CMake for Linux if: ${{matrix.target == 'Linux'}} working-directory: ${{github.workspace}} - run: cmake -S build/${{matrix.target}} -B $BUILD_DIRECTORY -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -DTHIRDPARTY_DIR=${{env.THIRDPARTY_DIR}} -DTHIRDPARTY_BUILD_DIR=${{env.THIRDPARTY_BUILD_DIR}} -DSUPERGENIUS_DIR=${{env.SUPERGENIUS_DIR}} -DGENIUSSDK_DIR=${{env.GENIUS_SDK_DIR}} -DABI_SUBFOLDER_NAME='/${{matrix.abi}}' -DBUILD_TESTS=ON + # Pin clang explicitly rather than relying on the /usr/bin/cc and + # /usr/bin/c++ update-alternatives symlinks: on AlmaLinux 8 + # /usr/bin/c++ is a real file owned by gcc-c++ (not an alternatives + # symlink like Debian's), so the update-alternatives takeover fails + # silently and CMake falls back to g++ 8.5, which cannot compile the + # code built here (same failure observed in SuperGenius run + # 35377550250). + run: cmake -S build/${{matrix.target}} -B $BUILD_DIRECTORY -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -DCMAKE_C_COMPILER=$(which clang) -DCMAKE_CXX_COMPILER=$(which clang++) -DTHIRDPARTY_DIR=${{env.THIRDPARTY_DIR}} -DTHIRDPARTY_BUILD_DIR=${{env.THIRDPARTY_BUILD_DIR}} -DSUPERGENIUS_DIR=${{env.SUPERGENIUS_DIR}} -DGENIUSSDK_DIR=${{env.GENIUS_SDK_DIR}} -DABI_SUBFOLDER_NAME='/${{matrix.abi}}' -DBUILD_TESTS=ON - name: Configure CMake for Windows if: ${{matrix.target == 'Windows'}} @@ -697,8 +802,10 @@ jobs: echo "RELEASE_TAG=$RELEASE_TAG" >> $GITHUB_ENV echo "IS_TAG_RELEASE=$IS_TAG_RELEASE" >> $GITHUB_ENV + # Also run for manual tag dispatches (env.IS_TAG) so a release tag can + # be created from any branch, matching the Upload step's condition. - name: Create release tag - if: github.ref_name == 'main' || github.ref_name == 'develop' + if: github.ref_name == 'main' || github.ref_name == 'develop' || env.IS_TAG == 'true' working-directory: ${{github.workspace}} id: create-release-tag shell: bash @@ -713,15 +820,31 @@ jobs: fi fi + # Branch releases own a per-platform tag (deleted + recreated above). + # A manual tag release is SHARED by every matrix job, so a release + # that already exists (previous run or a sibling job) must be reused. + if gh release view "$RELEASE_TAG" >/dev/null 2>&1; then + echo "Release $RELEASE_TAG already exists; uploading to it." + exit 0 + fi + echo "Creating release $RELEASE_TAG..." # gh release create --target handles both tag creation (via API) and release # creation in one call — avoids git push which requires workflows permission # when the tagged commit touches .github/workflows/ files. - gh release create "$RELEASE_TAG" \ - --target "${{ github.sha }}" \ - -t "${{ matrix.target }} ${GITHUB_REF_NAME} ${{ matrix.build-type }} build" \ - -n "Branch: ${GITHUB_REF_NAME} | SHA: ${{ github.sha }}" \ - $( [ "$IS_TAG_RELEASE" = "true" ] && echo "" || echo "--prerelease" ) + create_args=(--target "${{ github.sha }}" \ + -t "${{ matrix.target }} ${GITHUB_REF_NAME} ${{ matrix.build-type }} build" \ + -n "Branch: ${GITHUB_REF_NAME} | SHA: ${{ github.sha }}") + if [ "$IS_TAG_RELEASE" != "true" ]; then + create_args+=(--prerelease) + fi + if ! gh release create "$RELEASE_TAG" "${create_args[@]}"; then + # A sibling matrix job may have won the create race for the shared + # tag release; that is success, not failure. + sleep 15 + gh release view "$RELEASE_TAG" >/dev/null 2>&1 || exit 1 + echo "Release $RELEASE_TAG was created by a sibling matrix job; continuing." + fi - name: Upload file if: github.ref_name == 'main' || github.ref_name == 'develop' || env.IS_TAG == 'true' From d4ed6b6e492e6ddddf5afcb32e3e2c3bc8336f2b Mon Sep 17 00:00:00 2001 From: itsafuu Date: Mon, 21 Sep 2026 16:56:55 -0400 Subject: [PATCH 31/49] ci+build: explicit zkLLVM download step and consumer-side libsecret discovery MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run 35652579381 (Linux x86_64 Release) died at configure: supergeniusTargets.cmake:363 set_target_properties on sgns::secure_storage references PkgConfig::LIBSECRET, which the prebuilt SuperGenius package does not define — every consumer must run its own pkg_check_modules before find_package(SuperGenius), exactly as GeniusSDK's cmake/CommonBuildParameters.cmake does. Add that discovery (Linux only) to GCS's cmake/CommonBuildParameters.cmake, guarded the same way. Also replace D-09 (cmaketemplate's configure-time zkLLVM auto-download, which always fetches develop) with an explicit Download zkLLVM release step modeled on GeniusSDK/SuperGenius: zkllvm_tag dispatch input (falls back to the tag input), Release-type artifacts for non-Windows (cmakemplate's own fallback mapping), assigner binaries excluded, and -DZKLLVM_BUILD_DIR passed to the Linux configure. Non-Linux configures keep the auto-download path for now (Linux is the current focus). --- .github/workflows/cmake.yml | 75 ++++++++++++++++++++++++++++--- cmake/CommonBuildParameters.cmake | 10 +++++ 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index a044094..598ef42 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -35,6 +35,10 @@ on: description: "Tag for SuperGenius dependency (defaults to main tag if not specified)" required: false type: string + zkllvm_tag: + description: "Tag for zkLLVM dependency (defaults to main tag if not specified)" + required: false + type: string geniussdk_tag: description: "Tag for GeniusSDK dependency (defaults to main tag if not specified)" required: false @@ -523,9 +527,68 @@ jobs: echo "THIRDPARTY_DIR=$(pwd)" >> $GITHUB_ENV fi - # D-09: GCS has NO explicit zkLLVM step (deleted from the GeniusSDK - # template). cmaketemplate auto-downloads the headers-only zkLLVM at - # configure time; no assigner binaries are fetched. + # Replaces D-09 (cmaketemplate auto-download): an explicit zkLLVM step + # like GeniusSDK/SuperGenius have, so the release/tag is pinned and + # controllable (zkllvm_tag input) instead of always fetching develop at + # configure time. Only the headers/libs cmake needs are extracted — the + # assigner toolchain binaries are excluded to keep the workspace small. + - name: Download zkLLVM release + working-directory: ${{github.workspace}} + shell: bash + env: + ZKLLVM_TAG: ${{ github.event.inputs.zkllvm_tag || github.event.inputs.tag }} + run: | + # zkLLVM only publishes Release artifacts for non-Windows platforms + # (cmaketemplate's own fallback maps non-Windows Debug builds onto + # the Release zkLLVM), so ZK_TYPE is Release everywhere but Windows. + if [ "${{ matrix.target }}" = "Windows" ]; then + ZK_TYPE=${{ matrix.build-type }} + else + ZK_TYPE=Release + fi + + # Same branch/tag determination as the other dep downloads + if [ "$IS_TAG" == "true" ]; then + zkLLVM_branch="${ZKLLVM_TAG}" + elif [ "$CURRENT_BRANCH" == "main" ]; then + zkLLVM_branch="main" + else + zkLLVM_branch="develop" + fi + + echo "Using ${zkLLVM_branch} branch/tag from zkLLVM" + + mkdir zkLLVM + cd zkLLVM + + if [ "$IS_TAG" == "true" ]; then + tag_name="${zkLLVM_branch}" + else + if [ '${{matrix.abi}}' ]; then + tag_name="${{matrix.target}}-${{matrix.abi}}-${zkLLVM_branch}-${ZK_TYPE}" + else + tag_name="${{matrix.target}}-${zkLLVM_branch}-${ZK_TYPE}" + fi + fi + + if [ '${{matrix.abi}}' ]; then + ZKLLVM_FILE_NAME=${{matrix.target}}-${{matrix.abi}}-${ZK_TYPE}.tar.gz + ZKLLVM_BUILD_DIR=build/${{matrix.target}}/${ZK_TYPE}/${{matrix.abi}} + else + ZKLLVM_FILE_NAME=${{matrix.target}}-${ZK_TYPE}.tar.gz + ZKLLVM_BUILD_DIR=build/${{matrix.target}}/${ZK_TYPE} + fi + + gh release download ${tag_name} --repo GeniusVentures/zkLLVM -p "${ZKLLVM_FILE_NAME}" + + tar --exclude='*bin/assigner' --exclude='*bin/clang-zkllvm' --exclude='*bin/llvm-link-zkllvm' --exclude='*bin/transpiler' -xzf "${ZKLLVM_FILE_NAME}" + + if [ "${{ matrix.target }}" = "Windows" ]; then + # Convert Unix path to Windows path for Windows builds + echo "ZKLLVM_BUILD_DIR=$(pwd -W)/${ZKLLVM_BUILD_DIR}" >> $GITHUB_ENV + else + echo "ZKLLVM_BUILD_DIR=$(pwd)/${ZKLLVM_BUILD_DIR}" >> $GITHUB_ENV + fi - name: Download SuperGenius shell: bash @@ -686,7 +749,9 @@ jobs: # Configure deltas vs the GeniusSDK template: # - working-directory is ${{github.workspace}} (GCS checkout IS the root) - # - no -DZKLLVM_BUILD_DIR (D-09) + # - -DZKLLVM_BUILD_DIR from the explicit download step (zkllvm_tag + # input); Linux also uses it to pin clang (AlmaLinux /usr/bin/c++ is + # a real gcc-owned file, so update-alternatives fails silently) # - -DTHIRDPARTY_DIR / -DTHIRDPARTY_BUILD_DIR / -DSUPERGENIUS_DIR / # -DGENIUSSDK_DIR all use $(pwd)-computed GITHUB_ENV values: in # container jobs ${{github.workspace}} is the RUNNER host path, not @@ -728,7 +793,7 @@ jobs: # silently and CMake falls back to g++ 8.5, which cannot compile the # code built here (same failure observed in SuperGenius run # 35377550250). - run: cmake -S build/${{matrix.target}} -B $BUILD_DIRECTORY -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -DCMAKE_C_COMPILER=$(which clang) -DCMAKE_CXX_COMPILER=$(which clang++) -DTHIRDPARTY_DIR=${{env.THIRDPARTY_DIR}} -DTHIRDPARTY_BUILD_DIR=${{env.THIRDPARTY_BUILD_DIR}} -DSUPERGENIUS_DIR=${{env.SUPERGENIUS_DIR}} -DGENIUSSDK_DIR=${{env.GENIUS_SDK_DIR}} -DABI_SUBFOLDER_NAME='/${{matrix.abi}}' -DBUILD_TESTS=ON + run: cmake -S build/${{matrix.target}} -B $BUILD_DIRECTORY -DCMAKE_BUILD_TYPE=${{matrix.build-type}} -DCMAKE_C_COMPILER=$(which clang) -DCMAKE_CXX_COMPILER=$(which clang++) -DTHIRDPARTY_DIR=${{env.THIRDPARTY_DIR}} -DTHIRDPARTY_BUILD_DIR=${{env.THIRDPARTY_BUILD_DIR}} -DZKLLVM_BUILD_DIR=${{env.ZKLLVM_BUILD_DIR}} -DSUPERGENIUS_DIR=${{env.SUPERGENIUS_DIR}} -DGENIUSSDK_DIR=${{env.GENIUS_SDK_DIR}} -DABI_SUBFOLDER_NAME='/${{matrix.abi}}' -DBUILD_TESTS=ON - name: Configure CMake for Windows if: ${{matrix.target == 'Windows'}} diff --git a/cmake/CommonBuildParameters.cmake b/cmake/CommonBuildParameters.cmake index 713c320..0970aad 100644 --- a/cmake/CommonBuildParameters.cmake +++ b/cmake/CommonBuildParameters.cmake @@ -440,6 +440,16 @@ find_package(LLVM CONFIG REQUIRED) set(SUPERGENIUS_BUILD_DIR "${PROJECT_SUPER_ROOT}/SuperGenius/build/${BUILD_PLATFORM_NAME}/${CMAKE_BUILD_TYPE}${ABI_SUBFOLDER_NAME}" CACHE STRING "Default SuperGenius Build Directory") +# SuperGenius's exported sgns::secure_storage target links +# PkgConfig::LIBSECRET in its INTERFACE, but the prebuilt package does not +# re-run the pkg_check_modules() that defines it — every consumer must +# discover libsecret itself before find_package(SuperGenius) resolves the +# link interface (same as GeniusSDK's cmake/CommonBuildParameters.cmake). +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_package(PkgConfig) + pkg_check_modules(LIBSECRET REQUIRED IMPORTED_TARGET libsecret-1>=0.18.4) +endif() + # SuperGenius project set(evmrelay_DIR "${SUPERGENIUS_BUILD_DIR}/SuperGenius/lib/cmake/evmrelay/") set(SuperGenius_DIR "${SUPERGENIUS_BUILD_DIR}/SuperGenius/lib/cmake/SuperGenius/") From 45321a3925814147407dbc5d6439062462bc6a49 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Mon, 21 Sep 2026 17:50:32 -0400 Subject: [PATCH 32/49] ci+ffi: fix Linux test failures - keyring capability and MNN double registration Run 35654341772 (Linux builds now green, tests red): 1. test_gcs_ffi (Release+Debug): gnome-keyring-daemon dies with 'Operation not permitted' in the AlmaLinux container, so org.freedesktop.secrets never comes up and node boot fails with 'Account creation failed'. Add options: --cap-add=IPC_LOCK to the job container (SuperGenius precedent) so the daemon can mlock. 2. test_gcs_ffi_sdk / test_gcs_ffi_coldboot (Debug only): MNN aborts at Backend.cpp:135 MNNInsertExtraRuntimeCreator 'duplicate type'. These binaries link libgcs_ffi.so (which statically embeds MNN via gcs_core -> neoswarm_core) AND gcs_storage, whose SuperGenius chain (SGProcessors) whole-archives MNN into the executable - two MNN registries in one process. MNN's registry map is a function-local static (one per copy) but its registration functions are global, so on ELF the exe's symbols interpose into the .so and the second registration lands in the exe's already-populated map. Debug-only because MNN_ASSERT is plain assert (NDEBUG compiles it out); OSX historically passed because dyld's two-level namespace binds each image to its own symbols. Fix: -Wl,-Bsymbolic-functions on gcs_ffi so the dylib binds its internal MNN calls locally (functions only - vtables/typeinfo keep default binding). --- .github/workflows/cmake.yml | 5 +++++ src/ffi/CMakeLists.txt | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 598ef42..1295c27 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -308,6 +308,11 @@ jobs: runs-on: ${{ fromJson(needs.resolve-runners.outputs[matrix.runner_key]) }} container: image: ${{matrix.container}} + # IPC_LOCK lets gnome-keyring-daemon start inside the container + # (mlock for the keyring); without it the Linux ctest keyring wrapper + # fails with "Operation not permitted" and every node-booting test + # dies on "Account creation failed" (SuperGenius precedent). + options: --cap-add=IPC_LOCK credentials: username: ${{github.actor}} password: ${{secrets.GNUS_TOKEN_1}} diff --git a/src/ffi/CMakeLists.txt b/src/ffi/CMakeLists.txt index dd521ce..eeeebf9 100644 --- a/src/ffi/CMakeLists.txt +++ b/src/ffi/CMakeLists.txt @@ -17,6 +17,21 @@ target_include_directories(gcs_ffi PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ) target_link_libraries(gcs_ffi PRIVATE gcs_core gcs_proto) +# ELF symbol isolation: gcs_ffi statically embeds MNN (gcs_core -> +# neoswarm_core -> MNN::MNN), and executables that link BOTH this dylib and +# the SuperGenius static chain (SGProcessors whole-archives MNN into the +# exe) end up with two MNN runtime registries in one process. On ELF's flat +# namespace the exe's symbols interpose into the dylib, so the second +# registration trips MNNInsertExtraRuntimeCreator's "duplicate type" assert +# (Debug only — MNN_ASSERT is compiled out by NDEBUG in Release; macOS never +# saw this because dyld's two-level namespace binds each image to its own +# symbols). -Bsymbolic binds the dylib's internal references locally, giving +# its embedded MNN registry self-contained state, mirroring the macOS +# behavior. -Bsymbolic-functions (not plain -Bsymbolic) so vtables/typeinfo +# data symbols keep default binding — only the function calls matter here. +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + target_link_options(gcs_ffi PRIVATE "-Wl,-Bsymbolic-functions") +endif() # GCS_FFI_EXPORTS drives the gcs_* dllexport macro (gcs_core.h). # DART_SHARED_LIB makes DART_EXPORT resolve to __declspec(dllexport) so # Dart_InitializeApiDL (declared DART_EXPORT in dart_api_dl.h, defined in From d9eaa3352d63278b6e3edea2965df9494d12c8ba Mon Sep 17 00:00:00 2001 From: itsafuu Date: Tue, 22 Sep 2026 21:40:10 -0400 Subject: [PATCH 33/49] ci: decouple dependency downloads from the dispatch tag input The dep *_tag inputs (thirdparty/supergenius/zkllvm/geniussdk_tag) fell back to the dispatch 'tag' input, so any release-tag dispatch silently flipped every dependency into combined-release mode (e.g. releases/download/almadocker_test/Linux-x86_64-Release.tar.gz) instead of the normal per-platform branch releases (e.g. releases/download/Linux-x86_64-develop-Release/...). Now 'tag' only names the GCS release to create/upload to. Deps follow the branch flow unless a *_tag input is explicitly set: main builds pull the main releases, everything else (develop, feature branches, tag dispatches) pulls develop - matching the normal push-triggered behavior of this and the SuperGenius/GeniusSDK workflows. --- .github/workflows/cmake.yml | 177 +++++++++++++++++------------------- 1 file changed, 84 insertions(+), 93 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 1295c27..71c022c 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -24,23 +24,23 @@ on: workflow_dispatch: inputs: tag: - description: "Release tag (release to create/upload to; dep tags default to it)" + description: "GCS release tag to create/upload to (does NOT affect dep downloads - use the *_tag inputs)" required: false type: string thirdparty_tag: - description: "Tag for thirdparty dependency (defaults to main tag if not specified)" + description: "Combined thirdparty release tag (empty = normal main/develop per-platform releases)" required: false type: string supergenius_tag: - description: "Tag for SuperGenius dependency (defaults to main tag if not specified)" + description: "Combined SuperGenius release tag (empty = normal main/develop per-platform releases)" required: false type: string zkllvm_tag: - description: "Tag for zkLLVM dependency (defaults to main tag if not specified)" + description: "Combined zkLLVM release tag (empty = normal main/develop per-platform releases)" required: false type: string geniussdk_tag: - description: "Tag for GeniusSDK dependency (defaults to main tag if not specified)" + description: "Combined GeniusSDK release tag (empty = normal main/develop per-platform releases)" required: false type: string # Names must be build_-_: the skip step looks @@ -465,7 +465,7 @@ jobs: env: GITHUB_EVENT_INPUTS_TAG: ${{ github.event.inputs.tag }} GITHUB_EVENT_PULL_REQUEST_HEAD_REF: ${{ github.event.pull_request.head.ref }} - THIRDPARTY_TAG: ${{ github.event.inputs.thirdparty_tag || github.event.inputs.tag }} + THIRDPARTY_TAG: ${{ github.event.inputs.thirdparty_tag }} run: | # Determine current branch or tag if [ "${{ github.event_name }}" == "workflow_dispatch" ] && [ "${GITHUB_EVENT_INPUTS_TAG}" ]; then @@ -483,18 +483,29 @@ jobs: echo "CURRENT_BRANCH=${CURRENT_BRANCH}" >>$GITHUB_ENV echo "IS_TAG=${IS_TAG}" >>$GITHUB_ENV - # Determine thirdparty branch to use - if [ "$IS_TAG" == "true" ]; then - # For tags, use the thirdparty_tag input, falling back to the - # release tag itself (GeniusSDK/SuperGenius tag-build parity). - thirdparty_branch="${THIRDPARTY_TAG}" - elif [ "$CURRENT_BRANCH" == "main" ]; then - thirdparty_branch="main" + # Determine the thirdparty release to download from. An explicit + # thirdparty_tag names a COMBINED release (all platforms under one + # tag); otherwise the branch flow applies (main builds pull the + # main releases, everything else - including tag dispatches - + # pulls develop), where each platform is its own + # [-]-- release. The dispatch + # `tag` input no longer redirects dependency downloads. + if [ -n "${THIRDPARTY_TAG}" ]; then + dep_release="${THIRDPARTY_TAG}" else - thirdparty_branch="develop" + if [ "${CURRENT_BRANCH}" == "main" ]; then + dep_release_branch="main" + else + dep_release_branch="develop" + fi + if [ '${{matrix.abi}}' ]; then + dep_release="${{matrix.target}}-${{matrix.abi}}-${dep_release_branch}-${{matrix.build-type}}" + else + dep_release="${{matrix.target}}-${dep_release_branch}-${{matrix.build-type}}" + fi fi - echo "Using ${thirdparty_branch} for thirdparty" + echo "Downloading thirdparty from release ${dep_release}" if [ '${{matrix.abi}}' ]; then FILE_NAME=${{matrix.target}}-${{matrix.abi}}-${{matrix.build-type}}.tar.gz @@ -506,19 +517,7 @@ jobs: mkdir thirdparty cd thirdparty - if [ "$IS_TAG" == "true" ]; then - # For tags, use just the tag name - tag_name="${thirdparty_branch}" - else - # For branches, use the platform-branch-buildtype format - if [ '${{matrix.abi}}' ]; then - tag_name="${{matrix.target}}-${{matrix.abi}}-${thirdparty_branch}-${{matrix.build-type}}" - else - tag_name="${{matrix.target}}-${thirdparty_branch}-${{matrix.build-type}}" - fi - fi - - gh release download ${tag_name} --repo GeniusVentures/thirdparty -p "${FILE_NAME}" + gh release download "${dep_release}" --repo GeniusVentures/thirdparty -p "${FILE_NAME}" tar -zxf "${FILE_NAME}" # Set THIRDPARTY_BUILD_DIR to the actual location where files were extracted @@ -541,7 +540,7 @@ jobs: working-directory: ${{github.workspace}} shell: bash env: - ZKLLVM_TAG: ${{ github.event.inputs.zkllvm_tag || github.event.inputs.tag }} + ZKLLVM_TAG: ${{ github.event.inputs.zkllvm_tag }} run: | # zkLLVM only publishes Release artifacts for non-Windows platforms # (cmaketemplate's own fallback maps non-Windows Debug builds onto @@ -552,30 +551,30 @@ jobs: ZK_TYPE=Release fi - # Same branch/tag determination as the other dep downloads - if [ "$IS_TAG" == "true" ]; then - zkLLVM_branch="${ZKLLVM_TAG}" - elif [ "$CURRENT_BRANCH" == "main" ]; then - zkLLVM_branch="main" + # Same release resolution as the other dep downloads: an explicit + # zkllvm_tag names a combined release; otherwise branch flow (main + # builds pull main, everything else - including tag dispatches - + # pulls develop). + if [ -n "${ZKLLVM_TAG}" ]; then + dep_release="${ZKLLVM_TAG}" else - zkLLVM_branch="develop" + if [ "${CURRENT_BRANCH}" == "main" ]; then + dep_release_branch="main" + else + dep_release_branch="develop" + fi + if [ '${{matrix.abi}}' ]; then + dep_release="${{matrix.target}}-${{matrix.abi}}-${dep_release_branch}-${ZK_TYPE}" + else + dep_release="${{matrix.target}}-${dep_release_branch}-${ZK_TYPE}" + fi fi - echo "Using ${zkLLVM_branch} branch/tag from zkLLVM" + echo "Downloading zkLLVM from release ${dep_release}" mkdir zkLLVM cd zkLLVM - if [ "$IS_TAG" == "true" ]; then - tag_name="${zkLLVM_branch}" - else - if [ '${{matrix.abi}}' ]; then - tag_name="${{matrix.target}}-${{matrix.abi}}-${zkLLVM_branch}-${ZK_TYPE}" - else - tag_name="${{matrix.target}}-${zkLLVM_branch}-${ZK_TYPE}" - fi - fi - if [ '${{matrix.abi}}' ]; then ZKLLVM_FILE_NAME=${{matrix.target}}-${{matrix.abi}}-${ZK_TYPE}.tar.gz ZKLLVM_BUILD_DIR=build/${{matrix.target}}/${ZK_TYPE}/${{matrix.abi}} @@ -584,7 +583,7 @@ jobs: ZKLLVM_BUILD_DIR=build/${{matrix.target}}/${ZK_TYPE} fi - gh release download ${tag_name} --repo GeniusVentures/zkLLVM -p "${ZKLLVM_FILE_NAME}" + gh release download "${dep_release}" --repo GeniusVentures/zkLLVM -p "${ZKLLVM_FILE_NAME}" tar --exclude='*bin/assigner' --exclude='*bin/clang-zkllvm' --exclude='*bin/llvm-link-zkllvm' --exclude='*bin/transpiler' -xzf "${ZKLLVM_FILE_NAME}" @@ -598,37 +597,33 @@ jobs: - name: Download SuperGenius shell: bash env: - SUPERGENIUS_TAG: ${{ github.event.inputs.supergenius_tag || github.event.inputs.tag }} + SUPERGENIUS_TAG: ${{ github.event.inputs.supergenius_tag }} run: | - # Use same branch determination logic for SuperGenius - if [ "$IS_TAG" == "true" ]; then - # For tags, use the supergenius_tag input, falling back to the - # release tag itself. - sgns_branch="${SUPERGENIUS_TAG}" - elif [ "$CURRENT_BRANCH" == "main" ]; then - sgns_branch="main" + # Same release resolution as the other dep downloads: an explicit + # supergenius_tag names a combined release; otherwise branch flow + # (main builds pull main, everything else - including tag + # dispatches - pulls develop). + if [ -n "${SUPERGENIUS_TAG}" ]; then + dep_release="${SUPERGENIUS_TAG}" else - sgns_branch="develop" + if [ "${CURRENT_BRANCH}" == "main" ]; then + dep_release_branch="main" + else + dep_release_branch="develop" + fi + if [ '${{matrix.abi}}' ]; then + dep_release="${{matrix.target}}-${{matrix.abi}}-${dep_release_branch}-${{matrix.build-type}}" + else + dep_release="${{matrix.target}}-${dep_release_branch}-${{matrix.build-type}}" + fi fi - echo "Using ${sgns_branch} branch/tag from SuperGenius" + echo "Downloading SuperGenius from release ${dep_release}" mkdir SuperGenius cd SuperGenius - if [ "$IS_TAG" == "true" ]; then - # For tags, use just the tag name - tag_name="${sgns_branch}" - else - # For branches, use the platform-branch-buildtype format - if [ '${{matrix.abi}}' ]; then - tag_name="${{matrix.target}}-${{matrix.abi}}-${sgns_branch}-${{matrix.build-type}}" - else - tag_name="${{matrix.target}}-${sgns_branch}-${{matrix.build-type}}" - fi - fi - - gh release download ${tag_name} --repo GeniusVentures/SuperGenius -p "${FILE_NAME}" + gh release download "${dep_release}" --repo GeniusVentures/SuperGenius -p "${FILE_NAME}" tar -xzf "${FILE_NAME}" @@ -644,37 +639,33 @@ jobs: - name: Download GeniusSDK shell: bash env: - GENIUSSDK_TAG: ${{ github.event.inputs.geniussdk_tag || github.event.inputs.tag }} + GENIUSSDK_TAG: ${{ github.event.inputs.geniussdk_tag }} run: | - # Use same branch determination logic for GeniusSDK - if [ "$IS_TAG" == "true" ]; then - # For tags, use the geniussdk_tag input, falling back to the - # release tag itself. - sdk_branch="${GENIUSSDK_TAG}" - elif [ "$CURRENT_BRANCH" == "main" ]; then - sdk_branch="main" + # Same release resolution as the other dep downloads: an explicit + # geniussdk_tag names a combined release; otherwise branch flow + # (main builds pull main, everything else - including tag + # dispatches - pulls develop). + if [ -n "${GENIUSSDK_TAG}" ]; then + dep_release="${GENIUSSDK_TAG}" else - sdk_branch="develop" + if [ "${CURRENT_BRANCH}" == "main" ]; then + dep_release_branch="main" + else + dep_release_branch="develop" + fi + if [ '${{matrix.abi}}' ]; then + dep_release="${{matrix.target}}-${{matrix.abi}}-${dep_release_branch}-${{matrix.build-type}}" + else + dep_release="${{matrix.target}}-${dep_release_branch}-${{matrix.build-type}}" + fi fi - echo "Using ${sdk_branch} branch/tag from GeniusSDK" + echo "Downloading GeniusSDK from release ${dep_release}" mkdir GeniusSDK cd GeniusSDK - if [ "$IS_TAG" == "true" ]; then - # For tags, use just the tag name - tag_name="${sdk_branch}" - else - # For branches, use the platform-branch-buildtype format - if [ '${{matrix.abi}}' ]; then - tag_name="${{matrix.target}}-${{matrix.abi}}-${sdk_branch}-${{matrix.build-type}}" - else - tag_name="${{matrix.target}}-${sdk_branch}-${{matrix.build-type}}" - fi - fi - - gh release download ${tag_name} --repo GeniusVentures/GeniusSDK -p "${FILE_NAME}" + gh release download "${dep_release}" --repo GeniusVentures/GeniusSDK -p "${FILE_NAME}" tar -xzf "${FILE_NAME}" From 086ae2c791fabab601dc8cba88e87129989f4ebf Mon Sep 17 00:00:00 2001 From: itsafuu Date: Tue, 22 Sep 2026 22:26:12 -0400 Subject: [PATCH 34/49] fix(build): pin zlib to the vendored static lib before any module-mode find Run 35807447520: x86 Linux linked the container's system /usr/lib64/libz.so, arm64 recorded a multiarch libz.so ninja could not resolve at build time, and Windows died 'Could NOT find ZLIB (missing: ZLIB_LIBRARY)'. Root cause is ordering, not the vendored package: protobuf-config.cmake runs 'if(NOT ZLIB_FOUND) find_package(ZLIB)' and libssh2-config.cmake runs find_dependency(ZLIB) - both in MODULE mode, which ignores ZLIB_DIR and searches system paths. Our own 'find_package(ZLIB CONFIG REQUIRED)' sat AFTER Protobuf, so protobuf's module find ran first and grabbed the system lib. On Windows the vendored static lib is zs.lib (OUTPUT_NAME z + 's' suffix), a name FindZLIB never searches, so the module find could never succeed there. Fix: resolve the vendored CONFIG package BEFORE Protobuf and bridge its result into FindZLIB's own cache variables (ZLIB_LIBRARY/ZLIB_INCLUDE_DIR via NO_DEFAULT_PATH find_library/find_path inside the vendored tree). Every later find_package(ZLIB) - guarded or unguarded - now reuses the vendored static lib; the system search never runs. Verified against the real vendored Windows tree: protobuf's guarded call skips, libssh2's unguarded module re-run resolves zs.lib (1.3.2.1), and ZLIB::ZLIB stays an alias of ZLIB::ZLIBSTATIC. NEO-SWARM's CommonBuildParameters has the same after-protobuf ordering but inherits the bridged cache from the GCS-level configure that runs first. --- cmake/CommonBuildParameters.cmake | 39 +++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/cmake/CommonBuildParameters.cmake b/cmake/CommonBuildParameters.cmake index 0970aad..521c4f4 100644 --- a/cmake/CommonBuildParameters.cmake +++ b/cmake/CommonBuildParameters.cmake @@ -32,6 +32,38 @@ set(GTest_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/GTest/include") find_package(GTest CONFIG REQUIRED) include_directories(${GTest_INCLUDE_DIR}) +# -------------------------------------------------------- +# zlib (vendored, static) — MUST precede Protobuf: protobuf-config.cmake +# ("if(NOT ZLIB_FOUND) find_package(ZLIB)") and libssh2-config.cmake +# ("find_dependency(ZLIB)") further down both call find_package(ZLIB) in +# MODULE mode, which ignores ZLIB_DIR/ZLIB::ZLIBSTATIC and searches system +# paths. That is how CI run 35807447520 picked up the container's system +# libz (x86 linked /usr/lib64/libz.so; arm recorded a multiarch libz.so +# ninja then could not resolve), and how Windows died with "missing: +# ZLIB_LIBRARY" — the vendored static lib is zs.lib on Windows (OUTPUT_NAME +# z + "s" suffix), a name FindZLIB never searches. Resolve the vendored +# CONFIG package first, then bridge its result into FindZLIB's own cache +# variables so every later find_package(ZLIB) — guarded or not — reuses the +# vendored library and never touches the system. A later FindZLIB re-run is +# harmless: find_path/find_library early-out on the cached values, and its +# ZLIB::ZLIB creation is guarded by NOT TARGET (the vendored config already +# exposed the alias to ZLIB::ZLIBSTATIC). +set(ZLIB_ROOT "${THIRDPARTY_BUILD_DIR}/zlib") +set(ZLIB_DIR "${THIRDPARTY_BUILD_DIR}/zlib/lib/cmake/zlib") +find_package(ZLIB CONFIG REQUIRED) +# zs = Windows static name (OUTPUT_NAME z + "s" suffix); the same build sets +# CMAKE_DEBUG_POSTFIX "d" on Windows, so Debug trees ship zsd.lib. z = plain +# static name on Linux/macOS (libz.a). NO_DEFAULT_PATH keeps the search +# inside the vendored tree on every platform and configuration. +find_library(ZLIB_LIBRARY NAMES z zs zsd + PATHS "${THIRDPARTY_BUILD_DIR}/zlib/lib" + NO_DEFAULT_PATH REQUIRED) +find_path(ZLIB_INCLUDE_DIR zlib.h + PATHS "${THIRDPARTY_BUILD_DIR}/zlib/include" + NO_DEFAULT_PATH REQUIRED) +set(ZLIB_LIBRARIES "${ZLIB_LIBRARY}") +set(ZLIB_INCLUDE_DIRS "${ZLIB_INCLUDE_DIR}") + # -------------------------------------------------------- # protobuf (+ absl / utf8_range) — needed by NEO-SWARM src/proto add_proto_library if(NOT DEFINED absl_DIR) @@ -127,10 +159,9 @@ endif() find_package(Boost REQUIRED COMPONENTS container date_time filesystem random regex system thread log log_setup program_options json unit_test_framework coroutine) include_directories(${Boost_INCLUDE_DIRS}) -# zlib -set(ZLIB_ROOT "${THIRDPARTY_BUILD_DIR}/zlib") -set(ZLIB_DIR "${THIRDPARTY_BUILD_DIR}/zlib/lib/cmake/zlib") -find_package(ZLIB CONFIG REQUIRED) +# zlib: vendored discovery moved ABOVE Protobuf — see the zlib block near +# the top of this file. It must run before any package whose config calls +# find_package(ZLIB) in module mode (protobuf-config, libssh2-config). # fmt set(fmt_DIR "${THIRDPARTY_BUILD_DIR}/fmt/lib/cmake/fmt") From ce9ac9c3adf7c2a15fff324439565f277f7d7336 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Tue, 22 Sep 2026 23:09:35 -0400 Subject: [PATCH 35/49] ci+build: fix follow-on failures from run 35810498890 zlib vendoring itself worked (arm resolved thirdparty libz.a), but it exposed the next layer; five distinct issues, four fixed here: 1. Android configure death (regression): the NDK toolchain re-roots find_path into the sysroot (MODE_INCLUDE=ONLY; the Android shim only relaxes MODE_LIBRARY), so the bridging find_path(zlib.h) failed even though the tarball ships it. Add NO_CMAKE_FIND_ROOT_PATH to both bridging find_library/find_path calls. 2. Windows Debug: 'gh release download' died on a transient TLS handshake timeout before any build started. Wrap all four dep download calls in a 3-attempt retry helper. 3. Windows Release LNK2005: GeniusSDKInit/GeniusSDKShutdown defined in both gcs_ffi.dll and the static GeniusSDK.lib dragged in via gcs_storage (first Windows configure to get past zlib). Add /FORCE:MULTIPLE to test_gcs_ffi_sdk/test_gcs_ffi_coldboot (MSVC only) - gcs_ffi links first so the DLL wins, matching the documented Linux-side dylib-wins intent. 4. arm64 libsecret: the aarch64 image variant ships stale Debian-style pkg-config metadata advertising /usr/lib/aarch64-linux-gnu/libsecret-1.so, which does not exist in the container; ninja died on the phantom path. Guard in CommonBuildParameters.cmake: when pkg-config's advertised library file is missing, override PkgConfig::LIBSECRET's IMPORTED_LOCATION to the real /usr/lib64 copy. NOT fixed here (dep drift, upstream): x86 exit-time pthread abort after all tests pass, and OSX GeniusAccount private-key generation failures - both appeared with the 09-22/09-23 SuperGenius/GeniusSDK develop rebuilds; x86 Release was fully green in 35807447520 against older deps. Bisect in SuperGenius/GeniusSDK. --- .github/workflows/cmake.yml | 25 +++++++++++++--- cmake/CommonBuildParameters.cmake | 47 ++++++++++++++++++++++++------- test/CMakeLists.txt | 15 ++++++++++ 3 files changed, 73 insertions(+), 14 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 71c022c..c5d76cc 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -514,10 +514,27 @@ jobs: fi echo "FILE_NAME=${FILE_NAME}" >> $GITHUB_ENV + # Retry: gh release download occasionally dies on transient TLS + # handshake timeouts (run 35810498890 Windows Debug: + # 'Post "https://api.github.com/graphql": net/http: TLS handshake + # timeout'), which fails the job before any build starts. + download_with_retry() { + local attempt=1 + until "$@"; do + if [ "$attempt" -ge 3 ]; then + echo "::error::download failed after $attempt attempts: $*" + return 1 + fi + echo "download attempt $attempt failed; retrying in 15s..." + attempt=$((attempt + 1)) + sleep 15 + done + } + mkdir thirdparty cd thirdparty - gh release download "${dep_release}" --repo GeniusVentures/thirdparty -p "${FILE_NAME}" + download_with_retry gh release download "${dep_release}" --repo GeniusVentures/thirdparty -p "${FILE_NAME}" tar -zxf "${FILE_NAME}" # Set THIRDPARTY_BUILD_DIR to the actual location where files were extracted @@ -583,7 +600,7 @@ jobs: ZKLLVM_BUILD_DIR=build/${{matrix.target}}/${ZK_TYPE} fi - gh release download "${dep_release}" --repo GeniusVentures/zkLLVM -p "${ZKLLVM_FILE_NAME}" + download_with_retry gh release download "${dep_release}" --repo GeniusVentures/zkLLVM -p "${ZKLLVM_FILE_NAME}" tar --exclude='*bin/assigner' --exclude='*bin/clang-zkllvm' --exclude='*bin/llvm-link-zkllvm' --exclude='*bin/transpiler' -xzf "${ZKLLVM_FILE_NAME}" @@ -623,7 +640,7 @@ jobs: mkdir SuperGenius cd SuperGenius - gh release download "${dep_release}" --repo GeniusVentures/SuperGenius -p "${FILE_NAME}" + download_with_retry gh release download "${dep_release}" --repo GeniusVentures/SuperGenius -p "${FILE_NAME}" tar -xzf "${FILE_NAME}" @@ -665,7 +682,7 @@ jobs: mkdir GeniusSDK cd GeniusSDK - gh release download "${dep_release}" --repo GeniusVentures/GeniusSDK -p "${FILE_NAME}" + download_with_retry gh release download "${dep_release}" --repo GeniusVentures/GeniusSDK -p "${FILE_NAME}" tar -xzf "${FILE_NAME}" diff --git a/cmake/CommonBuildParameters.cmake b/cmake/CommonBuildParameters.cmake index 521c4f4..155cec9 100644 --- a/cmake/CommonBuildParameters.cmake +++ b/cmake/CommonBuildParameters.cmake @@ -55,12 +55,17 @@ find_package(ZLIB CONFIG REQUIRED) # CMAKE_DEBUG_POSTFIX "d" on Windows, so Debug trees ship zsd.lib. z = plain # static name on Linux/macOS (libz.a). NO_DEFAULT_PATH keeps the search # inside the vendored tree on every platform and configuration. +# NO_CMAKE_FIND_ROOT_PATH: cross-compile toolchains (NDK, apple) re-root +# find_path/find_library into the target sysroot and hide the host-side +# vendored tree — run 35810498890's Android jobs died at find_path even +# though the tarball ships zlib/include/zlib.h (the Android shim relaxes +# MODE_LIBRARY but not MODE_INCLUDE, which is why find_library passed). find_library(ZLIB_LIBRARY NAMES z zs zsd PATHS "${THIRDPARTY_BUILD_DIR}/zlib/lib" - NO_DEFAULT_PATH REQUIRED) + NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH REQUIRED) find_path(ZLIB_INCLUDE_DIR zlib.h PATHS "${THIRDPARTY_BUILD_DIR}/zlib/include" - NO_DEFAULT_PATH REQUIRED) + NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH REQUIRED) set(ZLIB_LIBRARIES "${ZLIB_LIBRARY}") set(ZLIB_INCLUDE_DIRS "${ZLIB_INCLUDE_DIR}") @@ -472,14 +477,36 @@ find_package(LLVM CONFIG REQUIRED) set(SUPERGENIUS_BUILD_DIR "${PROJECT_SUPER_ROOT}/SuperGenius/build/${BUILD_PLATFORM_NAME}/${CMAKE_BUILD_TYPE}${ABI_SUBFOLDER_NAME}" CACHE STRING "Default SuperGenius Build Directory") # SuperGenius's exported sgns::secure_storage target links -# PkgConfig::LIBSECRET in its INTERFACE, but the prebuilt package does not -# re-run the pkg_check_modules() that defines it — every consumer must -# discover libsecret itself before find_package(SuperGenius) resolves the -# link interface (same as GeniusSDK's cmake/CommonBuildParameters.cmake). -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - find_package(PkgConfig) - pkg_check_modules(LIBSECRET REQUIRED IMPORTED_TARGET libsecret-1>=0.18.4) -endif() + # PkgConfig::LIBSECRET in its INTERFACE, but the prebuilt package + # does not re-run the pkg_check_modules() that defines it — every + # consumer must discover libsecret itself before + # find_package(SuperGenius) resolves the link interface (same as + # GeniusSDK's cmake/CommonBuildParameters.cmake). + # GUARD: the aarch64 image variant ships stale Debian-style + # pkg-config metadata (libdir=/usr/lib/aarch64-linux-gnu) that does + # not exist in the container — run 35810498890's arm jobs recorded + # that nonexistent .so and ninja died on it. When the advertised + # library file is missing, fall back to the RPM's real location + # (/usr/lib64) via PKG_CONFIG_LIBDIR-less override variables. + if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_package(PkgConfig) + pkg_check_modules(LIBSECRET REQUIRED IMPORTED_TARGET libsecret-1>=0.18.4) + if(LIBSECRET_LINK_LIBRARIES) + list(GET LIBSECRET_LINK_LIBRARIES 0 _gcs_libsecret_first) + if(NOT EXISTS "${_gcs_libsecret_first}") + message(STATUS "libsecret pkg-config advertises missing ${_gcs_libsecret_first}; checking /usr/lib64") + find_library(_GCS_LIBSECRET_SO NAMES secret-1 + PATHS /usr/lib64 /usr/lib/aarch64-linux-gnu /usr/lib/x86_64-linux-gnu + NO_DEFAULT_PATH) + if(_GCS_LIBSECRET_SO) + message(STATUS "Overriding PkgConfig::LIBSECRET location to ${_GCS_LIBSECRET_SO}") + set_target_properties(PkgConfig::LIBSECRET PROPERTIES + IMPORTED_LOCATION "${_GCS_LIBSECRET_SO}") + else() + message(FATAL_ERROR "libsecret: pkg-config path missing and no system copy found") + endif() + endif() + endif() # SuperGenius project set(evmrelay_DIR "${SUPERGENIUS_BUILD_DIR}/SuperGenius/lib/cmake/evmrelay/") diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f949cb7..b17b5a3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -91,6 +91,17 @@ gcs_test(test_gcs_ffi_sdk test_gcs_ffi_sdk.cpp "gcs_ffi;gcs_storage;neoswa target_include_directories(test_gcs_ffi_sdk PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../src/ffi" ) +# MSVC only: this binary links gcs_ffi (whose DLL embeds and exports +# GeniusSDKInit/GeniusSDKShutdown) AND gcs_storage, whose PUBLIC chain +# drags the static GeniusSDK.lib defining the same symbols -> LNK2005 +# (run 35810498890, first Windows configure that got past zlib). /FORCE:MULTIPLE +# keeps the FIRST definition — gcs_ffi is listed first in target_link_libraries, +# so the DLL's exports win, which is exactly the documented Linux-side intent +# ("the dylib must win symbol resolution over the SDK archives"). Scoped to +# this binary only; other link combinations are unaffected. +if(MSVC) + target_link_options(test_gcs_ffi_sdk PRIVATE /FORCE:MULTIPLE) +endif() # Live-app regression (2026-09-19 UAT bug): a lone Flutter app reaches gcs_init # with NO GeniusSDK node in the process — gcs_init must boot the embedded node # itself (empty-mnemonic child-wallet path). This binary never boots the SDK @@ -101,3 +112,7 @@ gcs_test(test_gcs_ffi_coldboot test_gcs_ffi_coldboot.cpp "gcs_ffi;gcs_storage;ne target_include_directories(test_gcs_ffi_coldboot PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../src/ffi" ) +# Same MSVC dual-definition situation as test_gcs_ffi_sdk above. +if(MSVC) + target_link_options(test_gcs_ffi_coldboot PRIVATE /FORCE:MULTIPLE) +endif() From 555c69cbc10dc465d7bc8df8bcb0bbb140c0e3ed Mon Sep 17 00:00:00 2001 From: itsafuu Date: Wed, 23 Sep 2026 02:22:51 -0400 Subject: [PATCH 36/49] refactor(build): replace the zlib cache-variable bridge with PREFER_CONFIG The find_library/find_path bridging (ZLIB_LIBRARY/ZLIB_INCLUDE_DIR with NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH) worked but was over-engineered: it enumerated vendored lib names by hand (z/zs/zsd), needed sysroot escape flags for cross-compiles, and duplicated knowledge the vendored ZLIBConfig.cmake already owns. Follow SuperGenius's mechanism instead (build/CommonBuildParameters.cmake): plain find_package(ZLIB CONFIG REQUIRED) + CMAKE_FIND_PACKAGE_PREFER_CONFIG ON, in two narrow windows - around the Protobuf chain (protobuf-config's guarded module-mode find_package(ZLIB)) and around find_package(Libssh2) (libssh2-config's UNGUARDED find_dependency(ZLIB), the one that ran FindZLIB in module mode and grabbed system libz / died missing ZLIB_LIBRARY on Windows where the vendored zs.lib is not a searched name). PREFER_CONFIG makes even the unguarded call re-find the vendored ZLIBConfig, which links ZLIB::ZLIBSTATIC and knows zs.lib/zsd.lib/libz.a per configuration and platform. Verified against the real vendored tree: guarded call skips, libssh2 configure survives, ZLIB::ZLIB stays aliased to ZLIB::ZLIBSTATIC, and libssh2's interface resolves OpenSSL/system libs with no zlib leakage. Restores cleanly before the deliberately module-mode OpenSSL find. --- cmake/CommonBuildParameters.cmake | 88 +++++++++++++++++++------------ 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/cmake/CommonBuildParameters.cmake b/cmake/CommonBuildParameters.cmake index 155cec9..65f0e41 100644 --- a/cmake/CommonBuildParameters.cmake +++ b/cmake/CommonBuildParameters.cmake @@ -33,41 +33,32 @@ find_package(GTest CONFIG REQUIRED) include_directories(${GTest_INCLUDE_DIR}) # -------------------------------------------------------- -# zlib (vendored, static) — MUST precede Protobuf: protobuf-config.cmake -# ("if(NOT ZLIB_FOUND) find_package(ZLIB)") and libssh2-config.cmake -# ("find_dependency(ZLIB)") further down both call find_package(ZLIB) in -# MODULE mode, which ignores ZLIB_DIR/ZLIB::ZLIBSTATIC and searches system -# paths. That is how CI run 35807447520 picked up the container's system -# libz (x86 linked /usr/lib64/libz.so; arm recorded a multiarch libz.so -# ninja then could not resolve), and how Windows died with "missing: -# ZLIB_LIBRARY" — the vendored static lib is zs.lib on Windows (OUTPUT_NAME -# z + "s" suffix), a name FindZLIB never searches. Resolve the vendored -# CONFIG package first, then bridge its result into FindZLIB's own cache -# variables so every later find_package(ZLIB) — guarded or not — reuses the -# vendored library and never touches the system. A later FindZLIB re-run is -# harmless: find_path/find_library early-out on the cached values, and its -# ZLIB::ZLIB creation is guarded by NOT TARGET (the vendored config already -# exposed the alias to ZLIB::ZLIBSTATIC). +# zlib (vendored, static) — MUST precede Protobuf and Libssh2: both of their +# package configs call find_package(ZLIB) without CONFIG (protobuf-config +# guards it with "if(NOT ZLIB_FOUND)", libssh2-config's find_dependency is +# unguarded), which otherwise resolves to CMake's FindZLIB module and +# searches system paths — run 35807447520 linked the container's system +# libz on Linux and died "missing: ZLIB_LIBRARY" on Windows (the vendored +# static lib is zs.lib there, a name FindZLIB never searches). Same layout +# as SuperGenius build/CommonBuildParameters.cmake: resolve the vendored +# CONFIG package first (it exports ZLIB::ZLIBSTATIC and knows zs.lib / +# zsd.lib / libz.a itself), and set CMAKE_FIND_PACKAGE_PREFER_CONFIG below +# so every later module-mode-shaped call re-finds THIS config instead of +# the system. A re-find is harmless: the config's target creation is +# guarded by NOT TARGET and ZLIB::ZLIB stays an alias of ZLIB::ZLIBSTATIC. set(ZLIB_ROOT "${THIRDPARTY_BUILD_DIR}/zlib") set(ZLIB_DIR "${THIRDPARTY_BUILD_DIR}/zlib/lib/cmake/zlib") find_package(ZLIB CONFIG REQUIRED) -# zs = Windows static name (OUTPUT_NAME z + "s" suffix); the same build sets -# CMAKE_DEBUG_POSTFIX "d" on Windows, so Debug trees ship zsd.lib. z = plain -# static name on Linux/macOS (libz.a). NO_DEFAULT_PATH keeps the search -# inside the vendored tree on every platform and configuration. -# NO_CMAKE_FIND_ROOT_PATH: cross-compile toolchains (NDK, apple) re-root -# find_path/find_library into the target sysroot and hide the host-side -# vendored tree — run 35810498890's Android jobs died at find_path even -# though the tarball ships zlib/include/zlib.h (the Android shim relaxes -# MODE_LIBRARY but not MODE_INCLUDE, which is why find_library passed). -find_library(ZLIB_LIBRARY NAMES z zs zsd - PATHS "${THIRDPARTY_BUILD_DIR}/zlib/lib" - NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH REQUIRED) -find_path(ZLIB_INCLUDE_DIR zlib.h - PATHS "${THIRDPARTY_BUILD_DIR}/zlib/include" - NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH REQUIRED) -set(ZLIB_LIBRARIES "${ZLIB_LIBRARY}") -set(ZLIB_INCLUDE_DIRS "${ZLIB_INCLUDE_DIR}") +# Applied from here on (not before GTest): a global PREFER_CONFIG would +# also redirect other packages' module-mode finds (OpenSSL below is +# deliberately module-mode against the vendored OPENSSL_DIR tree), so keep +# the window as narrow as SuperGenius does around their own libssh2 call. +# (Stays ON through the Protobuf find below; restored right after it.) +set(_GCS_FIND_PREFER_CONFIG_PREV "") +if(DEFINED CMAKE_FIND_PACKAGE_PREFER_CONFIG) + set(_GCS_FIND_PREFER_CONFIG_PREV "${CMAKE_FIND_PACKAGE_PREFER_CONFIG}") +endif() +set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON) # -------------------------------------------------------- # protobuf (+ absl / utf8_range) — needed by NEO-SWARM src/proto add_proto_library @@ -98,6 +89,16 @@ if(EXISTS "${Protobuf_PROTOC_EXECUTABLE}") IMPORTED_LOCATION ${Protobuf_PROTOC_EXECUTABLE}) endif() +# Restore the find mode right after the ZLIB-sensitive window (the protobuf +# config find above): OpenSSL below is deliberately module-mode against the +# vendored OPENSSL_DIR tree and Boost discovery must keep its usual mode. +if(NOT "${_GCS_FIND_PREFER_CONFIG_PREV}" STREQUAL "") + set(CMAKE_FIND_PACKAGE_PREFER_CONFIG "${_GCS_FIND_PREFER_CONFIG_PREV}") +else() + unset(CMAKE_FIND_PACKAGE_PREFER_CONFIG) +endif() +unset(_GCS_FIND_PREFER_CONFIG_PREV) + # -------------------------------------------------------- # Set config of OpenSSL set(OPENSSL_DIR "${THIRDPARTY_BUILD_DIR}/openssl/build" CACHE PATH "Path to OpenSSL install folder") @@ -164,9 +165,7 @@ endif() find_package(Boost REQUIRED COMPONENTS container date_time filesystem random regex system thread log log_setup program_options json unit_test_framework coroutine) include_directories(${Boost_INCLUDE_DIRS}) -# zlib: vendored discovery moved ABOVE Protobuf — see the zlib block near -# the top of this file. It must run before any package whose config calls -# find_package(ZLIB) in module mode (protobuf-config, libssh2-config). +# zlib: vendored discovery lives ABOVE, before Protobuf — see that block. # fmt set(fmt_DIR "${THIRDPARTY_BUILD_DIR}/fmt/lib/cmake/fmt") @@ -398,8 +397,27 @@ set(xxHash_DIR "${THIRDPARTY_BUILD_DIR}/xxhash/lib/cmake/xxHash") find_package(xxHash CONFIG REQUIRED) # libssh2 +# PREFER_CONFIG (same mechanism as SuperGenius build/CommonBuildParameters.cmake +# "Prefer package config files while loading Libssh2's dependencies"): its +# config calls find_dependency(ZLIB) without CONFIG, which would otherwise +# run FindZLIB in module mode and pick up a system libz — or die on +# Windows where the vendored static lib (zs.lib) is not a name FindZLIB +# searches. Re-enable the flag around this call (it was restored to its +# pre-zlib value above); with it, the dependency re-finds the vendored +# ZLIBConfig and links ZLIB::ZLIBSTATIC. +set(_GCS_FIND_PREFER_CONFIG_PREV_2 "") +if(DEFINED CMAKE_FIND_PACKAGE_PREFER_CONFIG) + set(_GCS_FIND_PREFER_CONFIG_PREV_2 "${CMAKE_FIND_PACKAGE_PREFER_CONFIG}") +endif() +set(CMAKE_FIND_PACKAGE_PREFER_CONFIG ON) set(Libssh2_DIR "${THIRDPARTY_BUILD_DIR}/libssh2/lib/cmake/libssh2") find_package(Libssh2 CONFIG REQUIRED) +if(NOT "${_GCS_FIND_PREFER_CONFIG_PREV_2}" STREQUAL "") + set(CMAKE_FIND_PACKAGE_PREFER_CONFIG "${_GCS_FIND_PREFER_CONFIG_PREV_2}") +else() + unset(CMAKE_FIND_PACKAGE_PREFER_CONFIG) +endif() +unset(_GCS_FIND_PREFER_CONFIG_PREV_2) # AsyncIOManager set(AsyncIOManager_INCLUDE_DIR "${THIRDPARTY_BUILD_DIR}/AsyncIOManager/include") From 4bb1d9cee73ac9d4c91280b45548576365922512 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Wed, 23 Sep 2026 02:36:18 -0400 Subject: [PATCH 37/49] fix(ci): define download_with_retry in every download step ce9ac9c defined the retry helper only in the thirdparty download step but CALLED it from the zkLLVM/SuperGenius/GeniusSDK steps too - and each run: is a separate shell, so the function did not exist there: 'download_with_retry: command not found' broke every build in the run. (bash -n syntax checks cannot catch undefined function references, which is why validation passed.) Inline a self-contained definition in each of the four download steps. --- .github/workflows/cmake.yml | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index c5d76cc..827332f 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -600,6 +600,22 @@ jobs: ZKLLVM_BUILD_DIR=build/${{matrix.target}}/${ZK_TYPE} fi + # Retry inline in EVERY step: each run: is a separate shell, so a + # function defined in the thirdparty step does not exist here + # ('download_with_retry: command not found' broke every build). + download_with_retry() { + local attempt=1 + until "$@"; do + if [ "$attempt" -ge 3 ]; then + echo "::error::download failed after $attempt attempts: $*" + return 1 + fi + echo "download attempt $attempt failed; retrying in 15s..." + attempt=$((attempt + 1)) + sleep 15 + done + } + download_with_retry gh release download "${dep_release}" --repo GeniusVentures/zkLLVM -p "${ZKLLVM_FILE_NAME}" tar --exclude='*bin/assigner' --exclude='*bin/clang-zkllvm' --exclude='*bin/llvm-link-zkllvm' --exclude='*bin/transpiler' -xzf "${ZKLLVM_FILE_NAME}" @@ -637,6 +653,21 @@ jobs: echo "Downloading SuperGenius from release ${dep_release}" + # Retry inline (self-contained per step — see the zkLLVM step note: + # shell functions do not survive across separate run: steps). + download_with_retry() { + local attempt=1 + until "$@"; do + if [ "$attempt" -ge 3 ]; then + echo "::error::download failed after $attempt attempts: $*" + return 1 + fi + echo "download attempt $attempt failed; retrying in 15s..." + attempt=$((attempt + 1)) + sleep 15 + done + } + mkdir SuperGenius cd SuperGenius @@ -679,6 +710,21 @@ jobs: echo "Downloading GeniusSDK from release ${dep_release}" + # Retry inline (self-contained per step — see the zkLLVM step note: + # shell functions do not survive across separate run: steps). + download_with_retry() { + local attempt=1 + until "$@"; do + if [ "$attempt" -ge 3 ]; then + echo "::error::download failed after $attempt attempts: $*" + return 1 + fi + echo "download attempt $attempt failed; retrying in 15s..." + attempt=$((attempt + 1)) + sleep 15 + done + } + mkdir GeniusSDK cd GeniusSDK From 66325a6263e7f8d65febd68d9c2f4a3a5152d067 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Wed, 23 Sep 2026 02:41:31 -0400 Subject: [PATCH 38/49] fix(build): close the libsecret guard's missing endif ce9ac9c's arm64 libsecret guard opened if(CMAKE_SYSTEM_NAME STREQUAL "Linux") but its nesting ended at the LIBSECRET_LINK_LIBRARIES endif - the outer guard's endif() was missing, so every configure died with 'Flow control statements are not properly nested' at the next if(). Verified balanced file-wide (if/foreach/while/macro/function vs their enders); the PREFER_CONFIG windows are unaffected. --- cmake/CommonBuildParameters.cmake | 1 + 1 file changed, 1 insertion(+) diff --git a/cmake/CommonBuildParameters.cmake b/cmake/CommonBuildParameters.cmake index 65f0e41..5fd6a14 100644 --- a/cmake/CommonBuildParameters.cmake +++ b/cmake/CommonBuildParameters.cmake @@ -525,6 +525,7 @@ set(SUPERGENIUS_BUILD_DIR "${PROJECT_SUPER_ROOT}/SuperGenius/build/${BUILD_PLATF endif() endif() endif() + endif() # SuperGenius project set(evmrelay_DIR "${SUPERGENIUS_BUILD_DIR}/SuperGenius/lib/cmake/evmrelay/") From a993c6d1ac5c9d22afa5b45acea42be0acac36d2 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Wed, 23 Sep 2026 03:10:02 -0400 Subject: [PATCH 39/49] fix(build): move the libsecret phantom-path override after all SuperGenius finds Run 35827987810 (arm64): the guard fired correctly ("Overriding PkgConfig::LIBSECRET location to /usr/lib64/libsecret-1.so") but ninja still died on the phantom /usr/lib/aarch64-linux-gnu/libsecret-1.so. Root cause: SuperGenius bakes its own pkg_check_modules(LIBSECRET) into the installed SuperGeniusConfig (SUPERGENIUS_CONFIG_LIBSECRET, their a24beb24d from 09-17), and that re-runs at EVERY find_package(SuperGenius) - GCS's own call and NEO-SWARM's subtree one. Both re-create PkgConfig::LIBSECRET, clobbering any override set before them; IMPORTED_LOCATION from the last discovery wins at generate time. So the override now runs at the very bottom of this file, after both add_subdirectory() calls: read the target's final IMPORTED_LOCATION, and only when it points at a file that does not exist, repoint it at the real system copy (/usr/lib64 first - the RPM layout the el8 image actually ships). The early plain discovery stays (first-writer defines the target for the SuperGenius exports to consume). --- cmake/CommonBuildParameters.cmake | 77 ++++++++++++++++++------------- 1 file changed, 45 insertions(+), 32 deletions(-) diff --git a/cmake/CommonBuildParameters.cmake b/cmake/CommonBuildParameters.cmake index 5fd6a14..b09c056 100644 --- a/cmake/CommonBuildParameters.cmake +++ b/cmake/CommonBuildParameters.cmake @@ -494,38 +494,19 @@ find_package(LLVM CONFIG REQUIRED) set(SUPERGENIUS_BUILD_DIR "${PROJECT_SUPER_ROOT}/SuperGenius/build/${BUILD_PLATFORM_NAME}/${CMAKE_BUILD_TYPE}${ABI_SUBFOLDER_NAME}" CACHE STRING "Default SuperGenius Build Directory") -# SuperGenius's exported sgns::secure_storage target links - # PkgConfig::LIBSECRET in its INTERFACE, but the prebuilt package - # does not re-run the pkg_check_modules() that defines it — every - # consumer must discover libsecret itself before - # find_package(SuperGenius) resolves the link interface (same as - # GeniusSDK's cmake/CommonBuildParameters.cmake). - # GUARD: the aarch64 image variant ships stale Debian-style - # pkg-config metadata (libdir=/usr/lib/aarch64-linux-gnu) that does - # not exist in the container — run 35810498890's arm jobs recorded - # that nonexistent .so and ninja died on it. When the advertised - # library file is missing, fall back to the RPM's real location - # (/usr/lib64) via PKG_CONFIG_LIBDIR-less override variables. - if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - find_package(PkgConfig) - pkg_check_modules(LIBSECRET REQUIRED IMPORTED_TARGET libsecret-1>=0.18.4) - if(LIBSECRET_LINK_LIBRARIES) - list(GET LIBSECRET_LINK_LIBRARIES 0 _gcs_libsecret_first) - if(NOT EXISTS "${_gcs_libsecret_first}") - message(STATUS "libsecret pkg-config advertises missing ${_gcs_libsecret_first}; checking /usr/lib64") - find_library(_GCS_LIBSECRET_SO NAMES secret-1 - PATHS /usr/lib64 /usr/lib/aarch64-linux-gnu /usr/lib/x86_64-linux-gnu - NO_DEFAULT_PATH) - if(_GCS_LIBSECRET_SO) - message(STATUS "Overriding PkgConfig::LIBSECRET location to ${_GCS_LIBSECRET_SO}") - set_target_properties(PkgConfig::LIBSECRET PROPERTIES - IMPORTED_LOCATION "${_GCS_LIBSECRET_SO}") - else() - message(FATAL_ERROR "libsecret: pkg-config path missing and no system copy found") - endif() - endif() - endif() - endif() +# libsecret: pre-discovery (target override moved to the bottom of this +# file). SuperGenius's own cmake/config.cmake.in bakes a second +# pkg_check_modules(LIBSECRET ...) into the installed SuperGeniusConfig +# (their a24beb24d, since 09-17) that RE-RUNS at every +# find_package(SuperGenius) — GCS's own, and NEO-SWARM's subtree one — so a +# PkgConfig::LIBSECRET IMPORTED_LOCATION override placed here would be +# clobbered back to the phantom path before generate. IMPORTED_LOCATION is +# only read at generate time, so the fix now runs after every discovery +# site (see "libsecret phantom-path fix" at the bottom). +if(CMAKE_SYSTEM_NAME STREQUAL "Linux") + find_package(PkgConfig) + pkg_check_modules(LIBSECRET REQUIRED IMPORTED_TARGET libsecret-1>=0.18.4) +endif() # SuperGenius project set(evmrelay_DIR "${SUPERGENIUS_BUILD_DIR}/SuperGenius/lib/cmake/evmrelay/") @@ -647,3 +628,35 @@ if(BUILD_TESTS) endif() endif() +# -------------------------------------------------------- +# libsecret phantom-path fix — MUST be the LAST libsecret-related statement +# in this file. The aarch64 AlmaLinux-8 image variant ships stale Debian-style +# pkg-config metadata (libdir=/usr/lib/aarch64-linux-gnu) advertising +# /usr/lib/aarch64-linux-gnu/libsecret-1.so, which does not exist in the +# container; ninja then dies with "needed by ... missing and no known rule to +# make it" (runs 35810498890 and 35827987810). Earlier attempts patched +# PkgConfig::LIBSECRET right after the first pkg_check_modules — but +# SuperGenius's exported config embeds its own pkg_check_modules +# (SUPERGENIUS_CONFIG_LIBSECRET, their a24beb24d) that re-runs at every +# find_package(SuperGenius) (ours above AND NEO-SWARM's subtree one) and +# re-clobbers the target back to the phantom path before generate. Since +# IMPORTED_LOCATION is only read at generate time, running the override here — +# after all discovery sites — is the version that actually lands. +if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND TARGET PkgConfig::LIBSECRET) + get_target_property(_gcs_libsecret_loc PkgConfig::LIBSECRET IMPORTED_LOCATION) + if(_gcs_libsecret_loc AND NOT EXISTS "${_gcs_libsecret_loc}") + message(STATUS "libsecret target points at missing ${_gcs_libsecret_loc}; searching system paths") + find_library(_GCS_LIBSECRET_SO NAMES secret-1 + PATHS /usr/lib64 /usr/lib /usr/lib/aarch64-linux-gnu /usr/lib/x86_64-linux-gnu + NO_DEFAULT_PATH) + if(_GCS_LIBSECRET_SO) + message(STATUS "Pointing PkgConfig::LIBSECRET at ${_GCS_LIBSECRET_SO}") + set_target_properties(PkgConfig::LIBSECRET PROPERTIES + IMPORTED_LOCATION "${_GCS_LIBSECRET_SO}") + else() + message(FATAL_ERROR "libsecret: target location missing and no system copy found") + endif() + endif() + unset(_gcs_libsecret_loc) +endif() + From 603dddfff221749392d77c16940ae370c5ccc36b Mon Sep 17 00:00:00 2001 From: itsafuu Date: Wed, 23 Sep 2026 03:24:52 -0400 Subject: [PATCH 40/49] fix(build): rewrite the libsecret fix against INTERFACE_LINK_LIBRARIES Run 35830559548 (arm64, commit a993c6d): the bottom-of-file override never fired - its diagnostic never printed. Root cause: pkg_check_modules IMPORTED_TARGET creates an INTERFACE IMPORTED library, which links through INTERFACE_LINK_LIBRARIES (raw pkg-config strings, FindPkgConfig 330-338) and never reads IMPORTED_LOCATION. The previous override set a property nothing consumes, on both attempts (the pre-SuperGenius-find version and the post-subtrees version). The working fix: after ALL discovery sites (GCS's pkg_check_modules, the SuperGenius exported config's embedded one, NEO-SWARM's subtree find_package(SuperGenius)), read INTERFACE_LINK_LIBRARIES and replace absolute-path entries that do not exist on disk with a find_library hit under the RPM layout (/usr/lib64 first). Verified via a probe simulating the arm image's phantom-path .pc: phantom replaced, healthy paths pass through untouched. Guarded to Linux + existing target, so Windows/macOS (secure_storage uses wincred/Keychain there, no libsecret anywhere) are unaffected. --- cmake/CommonBuildParameters.cmake | 60 ++++++++++++++++++++----------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/cmake/CommonBuildParameters.cmake b/cmake/CommonBuildParameters.cmake index b09c056..906af20 100644 --- a/cmake/CommonBuildParameters.cmake +++ b/cmake/CommonBuildParameters.cmake @@ -634,29 +634,49 @@ endif() # pkg-config metadata (libdir=/usr/lib/aarch64-linux-gnu) advertising # /usr/lib/aarch64-linux-gnu/libsecret-1.so, which does not exist in the # container; ninja then dies with "needed by ... missing and no known rule to -# make it" (runs 35810498890 and 35827987810). Earlier attempts patched -# PkgConfig::LIBSECRET right after the first pkg_check_modules — but -# SuperGenius's exported config embeds its own pkg_check_modules -# (SUPERGENIUS_CONFIG_LIBSECRET, their a24beb24d) that re-runs at every -# find_package(SuperGenius) (ours above AND NEO-SWARM's subtree one) and -# re-clobbers the target back to the phantom path before generate. Since -# IMPORTED_LOCATION is only read at generate time, running the override here — -# after all discovery sites — is the version that actually lands. +# make it" (runs 35810498890, 35827987810, 35830559548). Earlier attempts: +# (1) patching right after the first pkg_check_modules — SuperGenius's exported +# config embeds its own pkg_check_modules (SUPERGENIUS_CONFIG_LIBSECRET, their +# a24beb24d) that re-runs at every find_package(SuperGenius), ours AND the +# NEO-SWARM subtree's, re-clobbering the target before generate; (2) patching +# IMPORTED_LOCATION at the bottom — a no-op, because pkg_check_modules's +# IMPORTED_TARGET creates an INTERFACE IMPORTED library that links through +# INTERFACE_LINK_LIBRARIES (raw pkg-config strings) and never reads +# IMPORTED_LOCATION (FindPkgConfig.cmake:330-338). The fix that actually +# lands: rewrite INTERFACE_LINK_LIBRARIES after ALL discovery sites — entries +# pointing at nonexistent files are replaced by the real system copy +# (/usr/lib64 first — the RPM layout the el8 image actually ships). if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND TARGET PkgConfig::LIBSECRET) - get_target_property(_gcs_libsecret_loc PkgConfig::LIBSECRET IMPORTED_LOCATION) - if(_gcs_libsecret_loc AND NOT EXISTS "${_gcs_libsecret_loc}") - message(STATUS "libsecret target points at missing ${_gcs_libsecret_loc}; searching system paths") - find_library(_GCS_LIBSECRET_SO NAMES secret-1 - PATHS /usr/lib64 /usr/lib /usr/lib/aarch64-linux-gnu /usr/lib/x86_64-linux-gnu - NO_DEFAULT_PATH) - if(_GCS_LIBSECRET_SO) - message(STATUS "Pointing PkgConfig::LIBSECRET at ${_GCS_LIBSECRET_SO}") - set_target_properties(PkgConfig::LIBSECRET PROPERTIES - IMPORTED_LOCATION "${_GCS_LIBSECRET_SO}") + get_target_property(_gcs_libsecret_libs PkgConfig::LIBSECRET INTERFACE_LINK_LIBRARIES) + set(_gcs_libsecret_fixed "") + set(_gcs_libsecret_changed FALSE) + foreach(_gcs_lib IN LISTS _gcs_libsecret_libs) + if(_gcs_lib MATCHES "^-l" OR NOT EXISTS "${_gcs_lib}") + # First missing absolute path triggers the system search (once). + if(NOT _gcs_libsecret_changed AND NOT DEFINED _GCS_LIBSECRET_SO AND _gcs_lib MATCHES "^/") + find_library(_GCS_LIBSECRET_SO NAMES secret-1 + PATHS /usr/lib64 /usr/lib /usr/lib/aarch64-linux-gnu /usr/lib/x86_64-linux-gnu + NO_DEFAULT_PATH) + endif() + if(_GCS_LIBSECRET_SO AND _gcs_lib MATCHES "^/") + message(STATUS "libsecret: replacing phantom ${_gcs_lib} with ${_GCS_LIBSECRET_SO}") + list(APPEND _gcs_libsecret_fixed "${_GCS_LIBSECRET_SO}") + set(_gcs_libsecret_changed TRUE) + else() + list(APPEND _gcs_libsecret_fixed "${_gcs_lib}") + endif() else() - message(FATAL_ERROR "libsecret: target location missing and no system copy found") + list(APPEND _gcs_libsecret_fixed "${_gcs_lib}") endif() + endforeach() + if(_gcs_libsecret_changed) + set_target_properties(PkgConfig::LIBSECRET PROPERTIES + INTERFACE_LINK_LIBRARIES "${_gcs_libsecret_fixed}") + message(STATUS "libsecret: INTERFACE_LINK_LIBRARIES now: ${_gcs_libsecret_fixed}") endif() - unset(_gcs_libsecret_loc) + unset(_gcs_libsecret_libs) + unset(_gcs_libsecret_fixed) + unset(_gcs_libsecret_changed) + unset(_gcs_lib) endif() From 1e55392ac75a1dced0b833b94fc8eb1b905445ac Mon Sep 17 00:00:00 2001 From: itsafuu Date: Wed, 23 Sep 2026 03:39:20 -0400 Subject: [PATCH 41/49] fix(build): repoint each phantom libsecret dep at its own library Run 35831659342: builds now start (progress), but test links died with "undefined reference to g_error_free" and "libglib-2.0.so.0: DSO missing from command line". Cause: the 603dddf rewrite resolved ONE replacement library (secret-1) and substituted it for every missing absolute path in INTERFACE_LINK_LIBRARIES. The arm image's broken .pc advertises the whole glib family as phantom paths - libsecret-1, libgio-2.0, libgobject-2.0, libgmodule-2.0 - so gio/gobject/gmodule all became libsecret-1.so copies and glib vanished from the link entirely. Fix: for each missing absolute path, take the entry's OWN basename (NAME_WE) and find_library that name under the RPM layout (/usr/lib64 first), substituting in place - gio stays gio. Also unset(_gcs_lib_real CACHE) per iteration: find_library caches by variable name, and a plain unset() leaves the cache entry, which would short-circuit every later iteration back to the first library found (reproduced in a probe). Probe-verified with all four glib-family stubs: each maps to its own library; healthy x86-style paths pass through untouched. --- cmake/CommonBuildParameters.cmake | 59 ++++++++++++++++++------------- 1 file changed, 35 insertions(+), 24 deletions(-) diff --git a/cmake/CommonBuildParameters.cmake b/cmake/CommonBuildParameters.cmake index 906af20..a6885af 100644 --- a/cmake/CommonBuildParameters.cmake +++ b/cmake/CommonBuildParameters.cmake @@ -632,39 +632,50 @@ endif() # libsecret phantom-path fix — MUST be the LAST libsecret-related statement # in this file. The aarch64 AlmaLinux-8 image variant ships stale Debian-style # pkg-config metadata (libdir=/usr/lib/aarch64-linux-gnu) advertising -# /usr/lib/aarch64-linux-gnu/libsecret-1.so, which does not exist in the -# container; ninja then dies with "needed by ... missing and no known rule to -# make it" (runs 35810498890, 35827987810, 35830559548). Earlier attempts: -# (1) patching right after the first pkg_check_modules — SuperGenius's exported -# config embeds its own pkg_check_modules (SUPERGENIUS_CONFIG_LIBSECRET, their -# a24beb24d) that re-runs at every find_package(SuperGenius), ours AND the -# NEO-SWARM subtree's, re-clobbering the target before generate; (2) patching -# IMPORTED_LOCATION at the bottom — a no-op, because pkg_check_modules's -# IMPORTED_TARGET creates an INTERFACE IMPORTED library that links through -# INTERFACE_LINK_LIBRARIES (raw pkg-config strings) and never reads -# IMPORTED_LOCATION (FindPkgConfig.cmake:330-338). The fix that actually -# lands: rewrite INTERFACE_LINK_LIBRARIES after ALL discovery sites — entries -# pointing at nonexistent files are replaced by the real system copy -# (/usr/lib64 first — the RPM layout the el8 image actually ships). +# paths like /usr/lib/aarch64-linux-gnu/libsecret-1.so that do not exist in +# the container. The .pc pulls in the whole glib family — libsecret-1, +# libgio-2.0, libgobject-2.0, libgmodule-2.0 — ALL with phantom paths, and +# each must be repointed at its OWN library (run 35831659342: a previous +# version substituted the single libsecret-1.so for every missing entry, +# losing glib entirely -> "undefined reference to g_error_free" + +# "libglib-2.0.so.0: DSO missing from command line" at test link). +# History of this fix: (1) patching right after our pkg_check_modules — +# SuperGenius's exported config embeds its own pkg_check_modules +# (SUPERGENIUS_CONFIG_LIBSECRET, their a24beb24d) that re-runs at every +# find_package(SuperGenius), ours AND the NEO-SWARM subtree's, re-clobbering +# the target; (2) patching IMPORTED_LOCATION — a no-op, because +# pkg_check_modules IMPORTED_TARGET creates an INTERFACE IMPORTED library +# linking through INTERFACE_LINK_LIBRARIES (FindPkgConfig.cmake:330-338). +# Correct mechanics: rewrite INTERFACE_LINK_LIBRARIES after ALL discovery +# sites; for each absolute-path entry that does not exist on disk, resolve +# that entry's own basename under the RPM layout (/usr/lib64 first — what +# the el8 image actually ships) and substitute in place. if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND TARGET PkgConfig::LIBSECRET) get_target_property(_gcs_libsecret_libs PkgConfig::LIBSECRET INTERFACE_LINK_LIBRARIES) set(_gcs_libsecret_fixed "") set(_gcs_libsecret_changed FALSE) foreach(_gcs_lib IN LISTS _gcs_libsecret_libs) - if(_gcs_lib MATCHES "^-l" OR NOT EXISTS "${_gcs_lib}") - # First missing absolute path triggers the system search (once). - if(NOT _gcs_libsecret_changed AND NOT DEFINED _GCS_LIBSECRET_SO AND _gcs_lib MATCHES "^/") - find_library(_GCS_LIBSECRET_SO NAMES secret-1 - PATHS /usr/lib64 /usr/lib /usr/lib/aarch64-linux-gnu /usr/lib/x86_64-linux-gnu - NO_DEFAULT_PATH) - endif() - if(_GCS_LIBSECRET_SO AND _gcs_lib MATCHES "^/") - message(STATUS "libsecret: replacing phantom ${_gcs_lib} with ${_GCS_LIBSECRET_SO}") - list(APPEND _gcs_libsecret_fixed "${_GCS_LIBSECRET_SO}") + if(_gcs_lib MATCHES "^/" AND NOT EXISTS "${_gcs_lib}") + # Repoint by the entry's OWN basename (libgio-2.0 must stay + # libgio-2.0) — one find_library per distinct missing library. + get_filename_component(_gcs_lib_name "${_gcs_lib}" NAME_WE) + find_library(_gcs_lib_real "${_gcs_lib_name}" + PATHS /usr/lib64 /usr/lib + NO_DEFAULT_PATH) + if(_gcs_lib_real) + message(STATUS "libsecret: repointing ${_gcs_lib} -> ${_gcs_lib_real}") + list(APPEND _gcs_libsecret_fixed "${_gcs_lib_real}") set(_gcs_libsecret_changed TRUE) else() + message(WARNING "libsecret: no replacement found for ${_gcs_lib}; keeping it") list(APPEND _gcs_libsecret_fixed "${_gcs_lib}") endif() + # unset(CACHE) is the critical half: find_library caches by + # variable name, and a plain unset() leaves the cache entry — + # every later iteration would then short-circuit to the FIRST + # library found (libsecret-1 substituting for gio/gobject/...). + unset(_gcs_lib_real CACHE) + unset(_gcs_lib_name) else() list(APPEND _gcs_libsecret_fixed "${_gcs_lib}") endif() From 3223787924fb4b630d122674d5ec94941053fc58 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Wed, 23 Sep 2026 04:10:44 -0400 Subject: [PATCH 42/49] fix(ci): actually wipe the workspace on self-hosted container jobs Root cause of the entire arm64 libsecret saga (runs 35810498890 through 35831659342): the Clean workspace step tested the ${{github.workspace}} TEMPLATE expression, which in container jobs expands to the RUNNER HOST path (/home/cicd/actions-runner/_work/...) - a directory that does not exist INSIDE the container. The arm runner (unlike x86, whose host maps it to /__w) leaves it unmapped, so the entire wipe silently skipped ("Workspace directory does not exist yet, skipping cleanup"). Consequence: stale dependency trees from earlier runs (the dev_almadocker/almadocker_test era) survived checkout and the mkdir+tar extract layered fresh tarballs over them; pkg_check_modules then resolved libsecret against leftover Debian-built artifacts, producing the phantom /usr/lib/aarch64-linux-gnu paths that exist nowhere in the AlmaLinux image. Verified end-to-end: a clean-room replication (real image, same digest; real dep tarballs; full configure) resolves libsecret to /lib64/* correctly - the image, the current tarballs, and pkg-config are all innocent. That is also why SuperGenius never sees this: their clean step rm -rfs the dep dirs by name, no workspace-path expansion involved. Fixes: - Clean workspace: use the runtime $GITHUB_WORKSPACE env var (set to the CONTAINER path inside the job) instead of the template expression - Belt and suspenders: every dep download step rm -rfs its own target dir (thirdparty/ zkLLVM/ SuperGenius/ GeniusSDK/) before extracting, so a future cleanup failure cannot poison the configure - Remove the libsecret INTERFACE_LINK_LIBRARIES workaround from cmake/CommonBuildParameters.cmake entirely: with a clean workspace the plain two-line pkg_check_modules (same as SuperGenius/GeniusSDK) is sufficient. Block balance verified; no workaround residue remains. --- .github/workflows/cmake.yml | 31 +++++++++--- cmake/CommonBuildParameters.cmake | 80 ++++--------------------------- 2 files changed, 34 insertions(+), 77 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 827332f..a00b376 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -411,10 +411,22 @@ jobs: echo "=== PRE-CLEANUP DEBUG ===" echo "Current working directory: $(pwd)" - echo "Workspace: ${{github.workspace}}" - - if [ -d "${{github.workspace}}" ]; then - cd "${{github.workspace}}" + # CRITICAL: use $GITHUB_WORKSPACE (the runtime env var), NOT the + # ${{github.workspace}} template expression. In container jobs the + # template expands to the RUNNER HOST path (/home//...), which + # does not exist inside the container — on the arm runner the whole + # wipe then silently skipped ("Workspace directory does not exist + # yet"), stale dep trees from earlier runs survived + # checkout+extract, and pkg_check_modules resolved libsecret to + # phantom Debian-style paths from the leftover artifacts + # (runs 35810498890..35831659342). $GITHUB_WORKSPACE is set to the + # CONTAINER path (/__w/...) inside the job, so the wipe actually + # runs where the build runs. $(pwd) equals it for run: steps. + WS="${GITHUB_WORKSPACE:-$(pwd)}" + echo "Workspace: ${WS}" + + if [ -d "${WS}" ]; then + cd "${WS}" echo "Changed to workspace directory: $(pwd)" echo "Contents:" ls -la @@ -433,8 +445,8 @@ jobs: fi echo "=== POST-CLEANUP DEBUG ===" - if [ -d "${{github.workspace}}" ]; then - cd "${{github.workspace}}" + if [ -d "${WS}" ]; then + cd "${WS}" echo "Final workspace contents:" ls -la fi @@ -531,6 +543,10 @@ jobs: done } + # Wipe any stale dir first: extracts must never layer over + # leftovers from earlier runs (root cause of the arm64 libsecret + # phantom-path failures — see the Clean workspace step note). + rm -rf thirdparty mkdir thirdparty cd thirdparty @@ -589,6 +605,7 @@ jobs: echo "Downloading zkLLVM from release ${dep_release}" + rm -rf zkLLVM mkdir zkLLVM cd zkLLVM @@ -668,6 +685,7 @@ jobs: done } + rm -rf SuperGenius mkdir SuperGenius cd SuperGenius @@ -725,6 +743,7 @@ jobs: done } + rm -rf GeniusSDK mkdir GeniusSDK cd GeniusSDK diff --git a/cmake/CommonBuildParameters.cmake b/cmake/CommonBuildParameters.cmake index a6885af..c90edef 100644 --- a/cmake/CommonBuildParameters.cmake +++ b/cmake/CommonBuildParameters.cmake @@ -494,15 +494,15 @@ find_package(LLVM CONFIG REQUIRED) set(SUPERGENIUS_BUILD_DIR "${PROJECT_SUPER_ROOT}/SuperGenius/build/${BUILD_PLATFORM_NAME}/${CMAKE_BUILD_TYPE}${ABI_SUBFOLDER_NAME}" CACHE STRING "Default SuperGenius Build Directory") -# libsecret: pre-discovery (target override moved to the bottom of this -# file). SuperGenius's own cmake/config.cmake.in bakes a second -# pkg_check_modules(LIBSECRET ...) into the installed SuperGeniusConfig -# (their a24beb24d, since 09-17) that RE-RUNS at every -# find_package(SuperGenius) — GCS's own, and NEO-SWARM's subtree one — so a -# PkgConfig::LIBSECRET IMPORTED_LOCATION override placed here would be -# clobbered back to the phantom path before generate. IMPORTED_LOCATION is -# only read at generate time, so the fix now runs after every discovery -# site (see "libsecret phantom-path fix" at the bottom). +# libsecret — same two lines SuperGenius/GeniusSDK use. The arm64 CI +# phantom-path drama (aarch64-linux-gnu paths that exist nowhere) was +# never an image or .pc problem: stale dep trees from earlier runs +# survived in the workspace because the cleanup step tested the RUNNER +# HOST path inside the container (see the workflow's Clean workspace +# step) and silently skipped; pkg_check_modules then resolved against +# leftover artifacts. With the workspace actually wiped, this plain +# discovery is sufficient — SuperGenius's exported config re-runs the +# same two lines itself, which is harmless (guarded target creation). if(CMAKE_SYSTEM_NAME STREQUAL "Linux") find_package(PkgConfig) pkg_check_modules(LIBSECRET REQUIRED IMPORTED_TARGET libsecret-1>=0.18.4) @@ -628,66 +628,4 @@ if(BUILD_TESTS) endif() endif() -# -------------------------------------------------------- -# libsecret phantom-path fix — MUST be the LAST libsecret-related statement -# in this file. The aarch64 AlmaLinux-8 image variant ships stale Debian-style -# pkg-config metadata (libdir=/usr/lib/aarch64-linux-gnu) advertising -# paths like /usr/lib/aarch64-linux-gnu/libsecret-1.so that do not exist in -# the container. The .pc pulls in the whole glib family — libsecret-1, -# libgio-2.0, libgobject-2.0, libgmodule-2.0 — ALL with phantom paths, and -# each must be repointed at its OWN library (run 35831659342: a previous -# version substituted the single libsecret-1.so for every missing entry, -# losing glib entirely -> "undefined reference to g_error_free" + -# "libglib-2.0.so.0: DSO missing from command line" at test link). -# History of this fix: (1) patching right after our pkg_check_modules — -# SuperGenius's exported config embeds its own pkg_check_modules -# (SUPERGENIUS_CONFIG_LIBSECRET, their a24beb24d) that re-runs at every -# find_package(SuperGenius), ours AND the NEO-SWARM subtree's, re-clobbering -# the target; (2) patching IMPORTED_LOCATION — a no-op, because -# pkg_check_modules IMPORTED_TARGET creates an INTERFACE IMPORTED library -# linking through INTERFACE_LINK_LIBRARIES (FindPkgConfig.cmake:330-338). -# Correct mechanics: rewrite INTERFACE_LINK_LIBRARIES after ALL discovery -# sites; for each absolute-path entry that does not exist on disk, resolve -# that entry's own basename under the RPM layout (/usr/lib64 first — what -# the el8 image actually ships) and substitute in place. -if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND TARGET PkgConfig::LIBSECRET) - get_target_property(_gcs_libsecret_libs PkgConfig::LIBSECRET INTERFACE_LINK_LIBRARIES) - set(_gcs_libsecret_fixed "") - set(_gcs_libsecret_changed FALSE) - foreach(_gcs_lib IN LISTS _gcs_libsecret_libs) - if(_gcs_lib MATCHES "^/" AND NOT EXISTS "${_gcs_lib}") - # Repoint by the entry's OWN basename (libgio-2.0 must stay - # libgio-2.0) — one find_library per distinct missing library. - get_filename_component(_gcs_lib_name "${_gcs_lib}" NAME_WE) - find_library(_gcs_lib_real "${_gcs_lib_name}" - PATHS /usr/lib64 /usr/lib - NO_DEFAULT_PATH) - if(_gcs_lib_real) - message(STATUS "libsecret: repointing ${_gcs_lib} -> ${_gcs_lib_real}") - list(APPEND _gcs_libsecret_fixed "${_gcs_lib_real}") - set(_gcs_libsecret_changed TRUE) - else() - message(WARNING "libsecret: no replacement found for ${_gcs_lib}; keeping it") - list(APPEND _gcs_libsecret_fixed "${_gcs_lib}") - endif() - # unset(CACHE) is the critical half: find_library caches by - # variable name, and a plain unset() leaves the cache entry — - # every later iteration would then short-circuit to the FIRST - # library found (libsecret-1 substituting for gio/gobject/...). - unset(_gcs_lib_real CACHE) - unset(_gcs_lib_name) - else() - list(APPEND _gcs_libsecret_fixed "${_gcs_lib}") - endif() - endforeach() - if(_gcs_libsecret_changed) - set_target_properties(PkgConfig::LIBSECRET PROPERTIES - INTERFACE_LINK_LIBRARIES "${_gcs_libsecret_fixed}") - message(STATUS "libsecret: INTERFACE_LINK_LIBRARIES now: ${_gcs_libsecret_fixed}") - endif() - unset(_gcs_libsecret_libs) - unset(_gcs_libsecret_fixed) - unset(_gcs_libsecret_changed) - unset(_gcs_lib) -endif() From 25eb983bd8ef0435510f5bfcf8dd873052aecba7 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Wed, 23 Sep 2026 04:18:13 -0400 Subject: [PATCH 43/49] ci: adopt SuperGenius's cleanup step shape (runner.workspace + named dirs) working-directory: ${{ runner.workspace }} as a STEP PROPERTY is runner-translated to the container path for container jobs, and the body wipes the checkout subdir by name with relative paths - same shape as SuperGenius's Clean workspace step, more readable than the GITHUB_WORKSPACE fallback it replaces (functionally equivalent: the step property avoids the host-path template expansion that caused the original silent-skip; the per-download-step rm -rf guards stay). --- .github/workflows/cmake.yml | 68 ++++++++++++------------------------- 1 file changed, 21 insertions(+), 47 deletions(-) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index a00b376..cfb1e69 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -401,55 +401,29 @@ jobs: - name: Clean workspace (self-hosted runners) if: ${{ runner.environment == 'self-hosted' }} + # SuperGenius pattern: working-directory as a STEP PROPERTY is + # runner-translated to the CONTAINER path for container jobs, and the + # body uses only relative paths. (Expanding ${{github.workspace}} + # inside the run: body instead yields the raw RUNNER HOST path — + # /home//actions-runner/_work/... — which does not exist in the + # container on the arm runner, so the whole wipe silently skipped and + # stale dev_almadocker-era dep trees poisoned every later configure: + # the arm64 libsecret phantom-path failures, runs 35810498890-35831659342.) + working-directory: ${{ runner.workspace }} shell: bash run: | - # Use sudo on Linux to handle root-owned files left by container jobs - SUDO="" - if [ "$(uname)" = "Linux" ]; then - SUDO="sudo" - fi - - echo "=== PRE-CLEANUP DEBUG ===" - echo "Current working directory: $(pwd)" - # CRITICAL: use $GITHUB_WORKSPACE (the runtime env var), NOT the - # ${{github.workspace}} template expression. In container jobs the - # template expands to the RUNNER HOST path (/home//...), which - # does not exist inside the container — on the arm runner the whole - # wipe then silently skipped ("Workspace directory does not exist - # yet"), stale dep trees from earlier runs survived - # checkout+extract, and pkg_check_modules resolved libsecret to - # phantom Debian-style paths from the leftover artifacts - # (runs 35810498890..35831659342). $GITHUB_WORKSPACE is set to the - # CONTAINER path (/__w/...) inside the job, so the wipe actually - # runs where the build runs. $(pwd) equals it for run: steps. - WS="${GITHUB_WORKSPACE:-$(pwd)}" - echo "Workspace: ${WS}" - - if [ -d "${WS}" ]; then - cd "${WS}" - echo "Changed to workspace directory: $(pwd)" - echo "Contents:" - ls -la - - # Full wipe, SuperGenius-style (their clean has NO git commands): - # container jobs leave root-owned files not just in the downloaded - # dirs but inside the submodule build trees (e.g. - # build/Linux/Release//CMakeFiles), which make `git clean - # -xfd` AND actions/checkout's own internal clean fail with - # permission errors on self-hosted runners. Removing everything - # with sudo lets checkout re-clone fresh. - echo "=== CLEANUP: full sudo wipe ===" - $SUDO rm -rf ./* ./.[!.]* || true - else - echo "Workspace directory does not exist yet, skipping cleanup" - fi - - echo "=== POST-CLEANUP DEBUG ===" - if [ -d "${WS}" ]; then - cd "${WS}" - echo "Final workspace contents:" - ls -la - fi + # Container jobs leave root-owned files behind on Linux. + SUDO=$([ "$(uname)" = "Linux" ] && echo sudo || echo) + # Keep the checkout directory itself, drop its contents: root-owned + # files live not just in the downloaded dep dirs (thirdparty/, + # zkLLVM/, SuperGenius/, GeniusSDK/ — wiped again per-step in the + # download steps) but inside the submodule build trees (e.g. + # build/Linux/Release//CMakeFiles), which make `git clean -xfd` + # AND actions/checkout's own internal clean fail with permission + # errors. The GCS checkout IS /GeniusCognitiveSystem + # (no path: override), so this is the repo root. + [ -d GeniusCognitiveSystem ] && $SUDO rm -rf GeniusCognitiveSystem/* GeniusCognitiveSystem/.[!.]* || true + ls -la - name: Checkout GCS uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd From d6f870068f7f9896159369baea267fb25fd5a3a1 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Wed, 23 Sep 2026 04:22:07 -0400 Subject: [PATCH 44/49] ci: also rm -rf the four dep dirs at workspace level in cleanup SuperGenius's step removes thirdparty/zkLLVM beside the checkout; GCS has four dep dirs (thirdparty zkLLVM SuperGenius GeniusSDK). They normally extract inside the checkout (covered by the contents wipe), but any that land beside it must not survive to poison a later configure. --- .github/workflows/cmake.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index cfb1e69..b9b639c 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -423,6 +423,12 @@ jobs: # errors. The GCS checkout IS /GeniusCognitiveSystem # (no path: override), so this is the repo root. [ -d GeniusCognitiveSystem ] && $SUDO rm -rf GeniusCognitiveSystem/* GeniusCognitiveSystem/.[!.]* || true + # Dep dirs by name at the workspace level too (SuperGenius-style): + # they normally extract INSIDE the checkout (covered above), but + # any that ever land beside it — older workflow layouts, the + # cmake-side GeniusSDK auto-download — must not survive to poison + # a later configure (the arm64 stale-artifact failure mode). + $SUDO rm -rf thirdparty zkLLVM SuperGenius GeniusSDK ls -la - name: Checkout GCS From 8908516155451dc05b2681a59477c858724f4e8c Mon Sep 17 00:00:00 2001 From: itsafuu Date: Wed, 23 Sep 2026 15:40:12 -0400 Subject: [PATCH 45/49] fix(ci): deploy vulkan-1.dll and gcs_ffi.dll next to Windows test exes Windows Release tests ran for the first time in 35904291986 and all 7 died with 0xc0000135 before main(): every test imports the vendored Vulkan loader DLL (via the SuperGenius static chain) and hosted windows-2022 runners ship no Vulkan runtime; the FFI tests also need gcs_ffi.dll, which links into gcs_src/ffi/Release, not gcs_test/Release. Port SuperGenius's addtest() POST_BUILD copy_if_different pattern into the gcs_test macro: copy VULKAN_RUNTIME_DLL (resolved in cmake/CommonBuildParameters.cmake) for every test, plus \$ for tests whose libs list contains gcs_ffi. copy_if_different makes repeat builds no-ops. Verified: full ctest -C Release 100% (7/7) with no PATH changes. --- test/CMakeLists.txt | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b17b5a3..0a0370e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -53,6 +53,29 @@ macro(gcs_test name sources libs) ${libs} ) add_test(NAME ${name} COMMAND ${name}) + # Windows: two DLLs the test exes import live outside their output dir + # (gcs_test/Release) and Windows has no build-tree rpath: + # - vulkan-1.dll: every test links the SuperGenius static chain, which + # pulls the vendored Vulkan loader; hosted CI runners (windows-2022 + # fallback) ship no Vulkan runtime, and without the DLL next to the exe + # the process dies with 0xc0000135 before main() runs (run 35904291986, + # all 7 tests). Ported from SuperGenius cmake/functions.cmake addtest(); + # VULKAN_RUNTIME_DLL is resolved in cmake/CommonBuildParameters.cmake. + # - gcs_ffi.dll: the FFI tests link the dylib, which the linker places in + # gcs_src/ffi/Release, not next to the tests. + # copy_if_different makes repeat builds no-ops. + if(WIN32) + if(VULKAN_RUNTIME_DLL) + add_custom_command(TARGET ${name} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "${VULKAN_RUNTIME_DLL}" "$/vulkan-1.dll") + endif() + if("${libs}" MATCHES "(^|;)gcs_ffi(;|$)") + add_custom_command(TARGET ${name} POST_BUILD + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + "$" "$") + endif() + endif() endmacro() # Moved GlobalDB lifecycle test (01-01) — exercises the GcsGlobalDb component From a5e5d6859c8e297a02e2d506cad1c6c68dd37445 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Wed, 23 Sep 2026 16:30:52 -0400 Subject: [PATCH 46/49] fix(test): skip static teardown in SDK-node-booting test binaries Run 35910956692 OSX Debug: test_gcs_global_db_sdk printed 1/1 PASSED, then aborted at process exit with 'mutex lock failed: Invalid argument' (twice) after main() returned. Same family as the 35904291986 aarch64 exit-time segfaults. Root cause (SuperGenius upstream): ScheduleBlockchainRetry and ScheduleMigrationRetry spawn detached sleeper threads (10s/5s) that re-enter StateTransition; ~GeniusNode joins only io_threads_ and upnp_thread, so a straggler mid-wake races static destruction. The shutdown_started_ gate is TOCTOU. The fast test (8.8s) shuts the SDK down inside the 10s retry window; the slow ones (46-89s) happen to land in quiet gaps. Mitigation per upstream precedent 3cf347e05 ('keep OpenSSL alive past main for node worker threads' - leaking state at exit is cheaper than the crash): new test/gcs_exit_main.hpp provides main() ending in std::_Exit after fflush, and the three SDK-booting binaries (test_gcs_global_db_sdk, test_gcs_ffi_sdk, test_gcs_ffi_coldboot) adopt it via a new OWN_MAIN option in the gcs_test macro (drops GTest::Main linkage for those targets). Assertions are complete when RUN_ALL_TESTS returns; skipping static teardown cannot mask a failure. Verified: all three pass locally with exit 0; full ctest -C Release 100% (7/7). The underlying retry-thread race remains open for an upstream SuperGenius fix (joinable retry threads or a CV instead of detached sleepers). --- test/CMakeLists.txt | 32 ++++++++++++++++------- test/gcs_exit_main.hpp | 45 +++++++++++++++++++++++++++++++++ test/test_gcs_ffi_coldboot.cpp | 5 ++++ test/test_gcs_ffi_sdk.cpp | 5 ++++ test/test_gcs_global_db_sdk.cpp | 5 ++++ 5 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 test/gcs_exit_main.hpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0a0370e..f708a52 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -41,17 +41,31 @@ endif() enable_testing() -# Helper macro +# Helper macro. Options: +# OWN_MAIN — the source provides main() (gcs_exit_main.hpp) instead of +# linking GTest::Main. Used by SDK-node-booting tests whose detached +# retry stragglers must never see static destruction (see header). macro(gcs_test name sources libs) + set( _GCS_TEST_OPTIONS "" ) + foreach( _arg IN ITEMS ${ARGN} ) + list( APPEND _GCS_TEST_OPTIONS ${_arg} ) + endforeach() add_executable(${name} ${sources}) # GTest::Main must precede ${libs}: SuperGenius static libs PUBLIC-export # Boost::unit_test_framework, whose archive defines main(). If it precedes # gtest_main, ld pulls unit_test_main.o and init_unit_test_suite goes undefined. - target_link_libraries(${name} PRIVATE - GTest::GTest - GTest::Main - ${libs} - ) + if( OWN_MAIN IN_LIST _GCS_TEST_OPTIONS ) + target_link_libraries(${name} PRIVATE + GTest::GTest + ${libs} + ) + else() + target_link_libraries(${name} PRIVATE + GTest::GTest + GTest::Main + ${libs} + ) + endif() add_test(NAME ${name} COMMAND ${name}) # Windows: two DLLs the test exes import live outside their output dir # (gcs_test/Release) and Windows has no build-tree rpath: @@ -89,7 +103,7 @@ gcs_test(test_gcs_storage test_gcs_global_db.cpp "gcs_storage;neoswarm_commo # GeniusSDKInit/Shutdown toggle a process-global node, which must not leak into # the option-C tests above. Links gcs_storage (PUBLIC: GeniusSDK_shared + # SuperGenius/GeniusSDK include dirs); skips when the node cannot boot. -gcs_test(test_gcs_global_db_sdk test_gcs_global_db_sdk.cpp "gcs_storage;neoswarm_common;sgns::crdt_globaldb") +gcs_test(test_gcs_global_db_sdk test_gcs_global_db_sdk.cpp "gcs_storage;neoswarm_common;sgns::crdt_globaldb" OWN_MAIN) # CORE-05 substrate smoke (01-04 Task 2): CoreSession lifecycle + topic + Put/Get # round-trip over an injected port-0 GossipPubSub. gcs_test(test_gcs_core_smoke test_gcs_core_smoke.cpp "gcs_core;gcs_storage;neoswarm_common") @@ -110,7 +124,7 @@ gcs_test(test_gcs_ffi test_gcs_ffi.cpp "gcs_ffi;gcs_proto") # dragged in transitively by gcs_storage (used only for the verification-side # injected-pubsub store). src/ffi is added for the vendored Dart API_DL # headers the fake post hook installs through the exported Dart_InitializeApiDL. -gcs_test(test_gcs_ffi_sdk test_gcs_ffi_sdk.cpp "gcs_ffi;gcs_storage;neoswarm_common;gcs_proto") +gcs_test(test_gcs_ffi_sdk test_gcs_ffi_sdk.cpp "gcs_ffi;gcs_storage;neoswarm_common;gcs_proto" OWN_MAIN) target_include_directories(test_gcs_ffi_sdk PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../src/ffi" ) @@ -131,7 +145,7 @@ endif() # directly; the cold-boot path IS the test. Same gcs_ffi-first linkage and # process-global-node isolation as test_gcs_ffi_sdk; skips only when a direct # SDK probe proves the environment cannot boot a node at all. -gcs_test(test_gcs_ffi_coldboot test_gcs_ffi_coldboot.cpp "gcs_ffi;gcs_storage;neoswarm_common;gcs_proto") +gcs_test(test_gcs_ffi_coldboot test_gcs_ffi_coldboot.cpp "gcs_ffi;gcs_storage;neoswarm_common;gcs_proto" OWN_MAIN) target_include_directories(test_gcs_ffi_coldboot PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../src/ffi" ) diff --git a/test/gcs_exit_main.hpp b/test/gcs_exit_main.hpp new file mode 100644 index 0000000..0b5a73a --- /dev/null +++ b/test/gcs_exit_main.hpp @@ -0,0 +1,45 @@ +/** + * @file gcs_exit_main.hpp + * @brief Own-main for test binaries that boot the GeniusSDK node. + * @details The node's async init chain leaves detached sleeper threads + * behind (SuperGenius ScheduleBlockchainRetry/ScheduleMigrationRetry + * sleep 10s/5s in std::thread(...).detach(); ~GeniusNode joins + * only io_threads_/upnp_thread). When a test shuts the SDK down + * while that chain is in flight — the fast tests land exactly + * inside the retry window — the stragglers wake after main() + * returns and lock mutexes that static destruction has already + * torn down: exit-time "mutex lock failed: Invalid argument" + * aborts AFTER all tests passed (CI 35910956692 OSX Debug, + * test_gcs_global_db_sdk; same family as the aarch64 exit + * segfaults in 35904291986). + * + * Wiring assertions are complete once RUN_ALL_TESTS() returns, + * so we skip static teardown entirely via std::_Exit — matching + * the upstream SuperGenius precedent 3cf347e05 ("leaking state + * at exit is cheaper than the crash"). C streams are flushed + * first: ctest pipes output (fully buffered), and _Exit skips + * the normal flush. + * + * Binaries using this header must link GTest::GTest only — + * gtest_main's main() would collide (OWN_MAIN in gcs_test()). + */ +#ifndef GCS_EXIT_MAIN_HPP +#define GCS_EXIT_MAIN_HPP + +#include + +#include + +#include + +int main( int argc, char **argv ) +{ + ::testing::InitGoogleTest( &argc, argv ); + const int result = RUN_ALL_TESTS(); + + std::fflush( stdout ); + std::fflush( stderr ); + std::_Exit( result ); +} + +#endif // GCS_EXIT_MAIN_HPP diff --git a/test/test_gcs_ffi_coldboot.cpp b/test/test_gcs_ffi_coldboot.cpp index 045e78d..477a167 100644 --- a/test/test_gcs_ffi_coldboot.cpp +++ b/test/test_gcs_ffi_coldboot.cpp @@ -27,6 +27,11 @@ #include "proto/gcs_chat.pb.h" #include "gcs_storage/common/logging.hpp" +// Own main() (std::_Exit after RUN_ALL_TESTS): the node's detached retry +// threads must never observe static destruction — see gcs_exit_main.hpp for +// the exit-time aborts this prevents (CI 35904291986 aarch64 segfaults). +#include "gcs_exit_main.hpp" + #include #include #include diff --git a/test/test_gcs_ffi_sdk.cpp b/test/test_gcs_ffi_sdk.cpp index ef8d542..6a1106f 100644 --- a/test/test_gcs_ffi_sdk.cpp +++ b/test/test_gcs_ffi_sdk.cpp @@ -23,6 +23,11 @@ #include "test_graphsync_network.hpp" #include "test_wait_condition.hpp" +// Own main() (std::_Exit after RUN_ALL_TESTS): the node's detached retry +// threads must never observe static destruction — see gcs_exit_main.hpp for +// the exit-time aborts this prevents (CI 35904291986 aarch64 segfaults). +#include "gcs_exit_main.hpp" + #include "gcs_storage/gcs_global_db.hpp" #include diff --git a/test/test_gcs_global_db_sdk.cpp b/test/test_gcs_global_db_sdk.cpp index 59716cf..a6c67b9 100644 --- a/test/test_gcs_global_db_sdk.cpp +++ b/test/test_gcs_global_db_sdk.cpp @@ -31,6 +31,11 @@ #include "GeniusSDK.hpp" +// Own main() (std::_Exit after RUN_ALL_TESTS): the node's detached retry +// threads must never observe static destruction — see header for the +// exit-time mutex abort this prevents (CI 35910956692 OSX Debug). +#include "gcs_exit_main.hpp" + namespace { /// Minimal soralog YAML — console sink, error level (mirrors From 23019251f326b784a61d2167feaf6b826cb201d6 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Wed, 23 Sep 2026 17:41:41 -0400 Subject: [PATCH 47/49] fix(test): inject in-memory secure storage for node-booting tests Run 35918456744 (both OSX configs): every node-booting test failed to create its account - 'GeniusAccount: Failed to generate Genius address from private key' - when the runner's OS keychain was unavailable. The option-C tests skipped (global_db_sdk 0.61s, ffi_sdk 0.81s, coldboot 0.64s); test_gcs_ffi hard-failed (no skip path - ABI contract says a valid config boots the node). Run-to-run flake: same deps passed in 35910956692. SuperGenius's own suite never touches the OS keychain: 38 test files inject MemorySecureStorage via GeniusAccount::SetSecureStorageFactory ('avoid OS keychain prompts during tests') - their OSX CI needs no keyring setup because of it. Port that house pattern to GCS. IMAGE RULE: the factory is a per-image static, so it must be set in the image that boots the node: - gcs_storage/common/test_env.hpp: header-only gcs_storage::test::UseInMemorySecureStorage() for exes that boot the SDK from their own static chain (test_gcs_global_db_sdk). - gcs_ffi exports gcs_use_test_secure_storage(): the embedded node boots inside the DLL, and an exe-side call cannot reach the DLL's static. Used by test_gcs_ffi, test_gcs_ffi_sdk, test_gcs_ffi_coldboot before their first gcs_init/GeniusSDKInit. Test seam only - secrets live for the process lifetime; never call from shipping binaries. Verified: all four node-booting tests boot real nodes locally (durations back to seconds, not fast-fail); ctest -C Release 100% (7/7). --- src/ffi/gcs_core.h | 15 ++++++ src/ffi/gcs_core_ffi.cpp | 9 ++++ src/lib/gcs_storage/common/test_env.hpp | 67 +++++++++++++++++++++++++ test/test_gcs_ffi.cpp | 9 ++++ test/test_gcs_ffi_coldboot.cpp | 9 ++++ test/test_gcs_ffi_sdk.cpp | 8 +++ test/test_gcs_global_db_sdk.cpp | 11 ++++ 7 files changed, 128 insertions(+) create mode 100644 src/lib/gcs_storage/common/test_env.hpp diff --git a/src/ffi/gcs_core.h b/src/ffi/gcs_core.h index 0c57173..677292b 100644 --- a/src/ffi/gcs_core.h +++ b/src/ffi/gcs_core.h @@ -148,6 +148,21 @@ extern "C" */ GCS_FFI_API void gcs_shutdown( GcsSession* session ) GCS_FFI_NOEXCEPT; + /** + * \brief TEST-ONLY SEAM: redirects this library's embedded GeniusSDK + * node to in-memory secure storage. + * + * The embedded node boots inside THIS library's image (gcs_init boots it + * when no host node exists), and GeniusAccount's secure-storage factory + * is a per-image static — so a test executable cannot redirect it from + * outside. This exported wrapper sets the factory on the library's copy + * (SuperGenius house pattern: inject MemorySecureStorage to avoid OS + * keychain prompts/flakiness — see gcs_storage/common/test_env.hpp). + * Call before the first gcs_init. Never call from shipping binaries: + * secrets live only for the process lifetime. + */ + GCS_FFI_API void gcs_use_test_secure_storage( void ) GCS_FFI_NOEXCEPT; + #if defined( __cplusplus ) } #endif diff --git a/src/ffi/gcs_core_ffi.cpp b/src/ffi/gcs_core_ffi.cpp index 04fe95b..10fa500 100644 --- a/src/ffi/gcs_core_ffi.cpp +++ b/src/ffi/gcs_core_ffi.cpp @@ -22,6 +22,7 @@ #include "lib/gcs_core.hpp" #include "lib/gcs_entity_store.hpp" #include "gcs_storage/common/logging.hpp" +#include "gcs_storage/common/test_env.hpp" #include "GeniusSDK.hpp" @@ -871,4 +872,12 @@ extern "C" TeardownSessionAndNode(); } } + + GCS_FFI_API void gcs_use_test_secure_storage( void ) GCS_FFI_NOEXCEPT + { + // The embedded node boots inside THIS image, so the factory must be + // set on THIS image's GeniusAccount static — see the IMAGE RULE in + // gcs_storage/common/test_env.hpp. Test seam only (header docs). + gcs_storage::test::UseInMemorySecureStorage(); + } } // extern "C" diff --git a/src/lib/gcs_storage/common/test_env.hpp b/src/lib/gcs_storage/common/test_env.hpp new file mode 100644 index 0000000..374e446 --- /dev/null +++ b/src/lib/gcs_storage/common/test_env.hpp @@ -0,0 +1,67 @@ +/** + * @file test_env.hpp + * @brief Test-environment helpers for GCS components that boot the + * GeniusSDK node. + * @details Node boot creates a GeniusAccount, whose default secure storage + * is OS-backed (Apple Keychain / libsecret / Windows DPAPI-adjacent + * file backends). On CI the OS keychain is unreliable: the macOS + * runner's login keychain is session-scoped and intermittently + * unavailable, so SecItemAdd fails, account creation dies with + * "Failed to generate Genius address from private key", and every + * node-booting test fails to boot (CI runs 35904291986 / + * 35918456744 — the option-C tests SKIP, test_gcs_ffi hard-fails). + * + * SuperGenius's own suite never touches the OS keychain: 38 test + * files call GeniusAccount::SetSecureStorageFactory with + * MemorySecureStorage ("inject in-memory secure storage to avoid + * OS keychain prompts during tests"). This header is that exact + * house pattern, exposed for GCS tests. + * + * IMAGE RULE: GeniusAccount::SetSecureStorageFactory sets a + * static in the TRANSLATION SCOPE THAT LINKS sgns_genius_account + * into the calling image. The node must boot in the SAME image + * where the factory is set: + * - tests that boot the SDK from their own static chain call + * UseInMemorySecureStorage() directly (this header); + * - tests that boot the node through gcs_ffi's embedded boot + * (gcs_init boots it inside the DLL) must use the DLL's + * exported gcs_use_test_secure_storage() so the factory is + * set on the DLL's copy — a call from the exe would set the + * exe's own static and never reach the embedded boot. + * + * Volatile-by-design: wallet/secret data lives only for the + * process lifetime (MemorySecureStorage keeps an in-process + * map). Test-only; never link into shipping binaries. + * @date 2026-09-23 + * @copyright (c) 2026 GNUS.AI + */ + +#ifndef GCS_STORAGE_TEST_ENV_HPP +#define GCS_STORAGE_TEST_ENV_HPP + +#include "account/GeniusAccount.hpp" +#include "local_secure_storage/impl/MemorySecureStorage.hpp" + +namespace gcs_storage::test +{ + /** + * @brief Redirects GeniusAccount secure storage to an in-memory backend. + * @details SuperGenius house pattern (38 upstream test files): replaces + * the OS keychain backend with MemorySecureStorage so node boot + * never prompts and never fails on a session-scoped CI keychain. + * Call BEFORE the first GeniusSDKInit/GcsGlobalDb::Initialize in + * the calling image. Idempotent: repeated calls reinstall the + * same factory lambda. + * + * Must be called from the image that boots the node — see the + * IMAGE RULE in the file header. + */ + inline void UseInMemorySecureStorage() + { + sgns::GeniusAccount::SetSecureStorageFactory( + []( const std::string &identifier ) -> std::shared_ptr + { return std::make_shared( identifier ); } ); + } +} // namespace gcs_storage::test + +#endif // GCS_STORAGE_TEST_ENV_HPP diff --git a/test/test_gcs_ffi.cpp b/test/test_gcs_ffi.cpp index 2208663..b49c3ac 100644 --- a/test/test_gcs_ffi.cpp +++ b/test/test_gcs_ffi.cpp @@ -57,6 +57,15 @@ namespace gcs::test protected: void SetUp() override { + // The embedded node boots INSIDE gcs_ffi's image (gcs_init boots + // it), so the secure-storage factory must be set on the DLL's + // copy — hence the exported seam, not the C++ helper (IMAGE RULE, + // gcs_storage/common/test_env.hpp). Before any gcs_init; without + // it the OS keychain makes account creation flaky on CI runners + // (runs 35904291986 / 35918456744: "Failed to generate Genius + // address from private key"). + gcs_use_test_secure_storage(); + const auto *info = ::testing::UnitTest::GetInstance()->current_test_info(); const auto uniqueSalt = std::chrono::steady_clock::now().time_since_epoch().count(); m_tempPath = ( std::filesystem::temp_directory_path() diff --git a/test/test_gcs_ffi_coldboot.cpp b/test/test_gcs_ffi_coldboot.cpp index 477a167..545339c 100644 --- a/test/test_gcs_ffi_coldboot.cpp +++ b/test/test_gcs_ffi_coldboot.cpp @@ -251,6 +251,15 @@ namespace gcs::test void SetUp() override { + // The cold-boot path boots the node INSIDE gcs_ffi's image (the + // test itself boots nothing — that IS the test), so the + // secure-storage factory must be set on the DLL's copy via the + // exported seam (IMAGE RULE, gcs_storage/common/test_env.hpp). + // Avoids the flaky OS keychain on CI runners (35904291986 / + // 35918456744: "Failed to generate Genius address from private + // key" → skip instead of exercising the cold-boot path). + gcs_use_test_secure_storage(); + const auto *info = ::testing::UnitTest::GetInstance()->current_test_info(); const auto uniqueSalt = std::chrono::steady_clock::now().time_since_epoch().count(); m_tempPath = ( std::filesystem::temp_directory_path() diff --git a/test/test_gcs_ffi_sdk.cpp b/test/test_gcs_ffi_sdk.cpp index 6a1106f..822e997 100644 --- a/test/test_gcs_ffi_sdk.cpp +++ b/test/test_gcs_ffi_sdk.cpp @@ -242,6 +242,14 @@ namespace gcs::test void SetUp() override { + // The node boots through gcs_ffi's exported GeniusSDKInit (dylib + // wins by linkage order), so the secure-storage factory must be + // set inside the DLL's image — the exported seam, not the C++ + // helper (IMAGE RULE, gcs_storage/common/test_env.hpp). Avoids + // the flaky OS keychain on CI runners (35904291986 / 35918456744: + // "Failed to generate Genius address from private key" → skip). + gcs_use_test_secure_storage(); + const auto *info = ::testing::UnitTest::GetInstance()->current_test_info(); const auto uniqueSalt = std::chrono::steady_clock::now().time_since_epoch().count(); m_tempPath = ( std::filesystem::temp_directory_path() diff --git a/test/test_gcs_global_db_sdk.cpp b/test/test_gcs_global_db_sdk.cpp index a6c67b9..7fd3b0a 100644 --- a/test/test_gcs_global_db_sdk.cpp +++ b/test/test_gcs_global_db_sdk.cpp @@ -28,6 +28,12 @@ #include #include "gcs_storage/gcs_global_db.hpp" +// SuperGenius house pattern (38 upstream test files): in-memory secure +// storage instead of the OS keychain — CI runners (session-scoped macOS +// login keychain) make SecItemAdd flaky and account creation fails (runs +// 35904291986 / 35918456744). This binary boots the node from its own +// static chain, so the direct C++ call sets the right image's factory. +#include "gcs_storage/common/test_env.hpp" #include "GeniusSDK.hpp" @@ -77,6 +83,11 @@ namespace sgns::neoswarm::storage::test protected: static void SetUpTestSuite() { + // Before any account exists: redirect secure storage away from the + // OS keychain (see test_env.hpp header — the IMAGE RULE is why this + // binary calls the C++ helper, not the FFI seam). + gcs_storage::test::UseInMemorySecureStorage(); + auto loggerConfigurator = std::make_shared(); auto configFromYaml = std::make_shared( loggerConfigurator, std::string{ kLoggingYaml } ); From 909a02eed72d619c9b7466f3e2698ec3dbfdebdb Mon Sep 17 00:00:00 2001 From: itsafuu Date: Wed, 23 Sep 2026 18:13:27 -0400 Subject: [PATCH 48/49] fix(test): own main for test_gcs_ffi - embedded node boots via gcs_init Run 35924087303 Linux x86_64 Debug: test_gcs_ffi printed 8/8 PASSED, then aborted at process exit with 'pthread lock: Invalid argument' - the same detached-retry-straggler exposure as the SDK-booting binaries (SuperGenius ScheduleBlockchainRetry sleeps 10s in a detached thread; ~GeniusNode joins only io_threads_/upnp_thread), but this binary was left on gtest_main when OWN_MAIN was introduced: gcs_init boots the embedded node INSIDE gcs_ffi's image (twice - the repeated-init case), so each shutdown can land inside the retry window. Passed in prior runs by timing luck (Linux Debug is the widest window). Same treatment as a5e5d68: test/gcs_exit_main.hpp main() ends in std::_Exit after RUN_ALL_TESTS + fflush - assertions are complete when RUN_ALL_TESTS returns, so static-teardown skipping cannot mask a failure. Verified: test_gcs_ffi passes locally with exit 0; ctest -C Release 100% (7/7). --- test/CMakeLists.txt | 2 +- test/test_gcs_ffi.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f708a52..451d37c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -114,7 +114,7 @@ gcs_test(test_gcs_entities test_gcs_entities.cpp "gcs_core;gcs_storage;neoswarm_ # FFI ABI smoke (01-04 Task 3): config-bytes gcs_init failure classes, arg # validation, shutdown-null. Also links gcs_proto so the test can serialize a # gcs::chat::GcsConfig via gcs_chat.pb.h (D-29 codec-tagged init bytes). -gcs_test(test_gcs_ffi test_gcs_ffi.cpp "gcs_ffi;gcs_proto") +gcs_test(test_gcs_ffi test_gcs_ffi.cpp "gcs_ffi;gcs_proto" OWN_MAIN) # CR-01 regression (01 code review): two init/send/shutdown cycles against the # SAME db_path — session A's persisted records must survive session B. Own # binary for the same reason as test_gcs_global_db_sdk (GeniusSDKInit toggles diff --git a/test/test_gcs_ffi.cpp b/test/test_gcs_ffi.cpp index b49c3ac..273be62 100644 --- a/test/test_gcs_ffi.cpp +++ b/test/test_gcs_ffi.cpp @@ -26,6 +26,14 @@ #include "ffi/gcs_core.h" #include "proto/gcs_chat.pb.h" +// Own main() (std::_Exit after RUN_ALL_TESTS): gcs_init boots the embedded +// node INSIDE gcs_ffi's image, so this binary has the same exit-time +// straggler exposure as the SDK-booting tests — the node's detached retry +// threads must never observe static destruction (run 35924087303 x86 Debug: +// 8/8 passed, then "pthread lock: Invalid argument" abort at exit; see +// gcs_exit_main.hpp). +#include "gcs_exit_main.hpp" + #include #include #include From e4ae25b70f91f7490ff0292137753b0c942ee011 Mon Sep 17 00:00:00 2001 From: itsafuu Date: Wed, 23 Sep 2026 19:22:12 -0400 Subject: [PATCH 49/49] ci: apt-get update before apt install on hosted Ubuntu runners GitHub-hosted ubuntu-24.04 runners (Android jobs when the self-hosted boxes are busy) ship an apt list baked at image-build time. security.ubuntu.com moves noble-updates packages forward continuously, so a stale index resolves package versions whose .deb URLs now 404 - apt install dies with 'Unable to fetch some archives' exit 100 (run 35928995199, gstreamer/glib noble-updates packages). Self-hosted boxes refresh their own lists, so only the hosted path is affected. Refresh the index before installing, best-effort (|| true) so a transient mirror hiccup fails at install time with a clearer error instead of at the update step. --- .github/workflows/cmake.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index b9b639c..b5ba2e0 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -760,6 +760,16 @@ jobs: sudo dnf install -y ccache ninja-build vulkan-loader-devel libsecret-devel libzstd-devel dbus gnome-keyring fi else + # Refresh the package index before installing: GitHub-hosted + # Ubuntu runners ship an apt list baked at image-build time, and + # security.ubuntu.com moves noble-updates packages forward + # continuously — a stale index fetches .deb URLs that now 404 + # ("Unable to fetch some archives", exit 100; observed on the + # Android jobs when they land on hosted ubuntu-24.04). Self-hosted + # boxes refresh their own lists, so only the hosted path needs + # this. Best-effort: a transient mirror failure should fail the + # install below, not the update itself. + sudo apt-get update || true sudo apt install ccache ninja-build libvulkan-dev libzstd-dev libsecret-1-dev dbus gnome-keyring -y fi echo "CMAKE_GENERATOR=Ninja" >> $GITHUB_ENV