monorepo finale: relocate crates, monitor release pipeline, docs overhaul#1234
monorepo finale: relocate crates, monitor release pipeline, docs overhaul#1234lollipopkit wants to merge 84 commits into
Conversation
- 删除 Go 实现(cmd/model/runner/web/res/main.go) - 补齐推送限流与 Go 兼容 GET /status(TODO: App 迁移后删除) - Size/Threshold 对齐 Go 语义,tests/go_compat.rs 锁定行为 - 修复 macOS PhysMem 解析与磁盘重复统计 - workspace 拆分,crates/sbm_parser:纯解析 + 命令清单单一事实来源 - 解析对齐 Dart 实现(lsblk JSON/FreeBSD/Windows WMI),tests/dart_compat.rs 移植 App fixture 33+ 用例,Windows 实机(lkd)验证通过
- flutter_rust_bridge v2 + cargokit 集成(rust/ = sbm_ffi,rust_builder/) - API:parse_status_json / command_specs / separator,解析纯函数无状态 - test/frb_parser_test.dart:FFI 与 Dart 解析器同 fixture 双跑一致 - 移除 packages/server_box_monitor submodule(旧 Go 版,Phase 3 由 monorepo 取代)
- crates/sbm_parser 提升至仓库根,根 Cargo workspace(crates/rust/monitor) - rust(sbm_ffi)与 monitor 路径依赖指向 ../crates/sbm_parser - monitor/Dockerfile 改以 monorepo 根为构建上下文 - CLAUDE.md 增补 monorepo 布局;FRB 测试指向根 target/
- 新增 conn/uptime/sys/host/cpuBrand/diskio/battery/sensors 解析 - 新增 GPU(nvidia-smi XML via roxmltree、amd-smi JSON)与 SMART(smartctl JSON) - 命令清单补全并加 core 标记:monitor 周期采集只跑轻量子集, GPU/SMART 等高开销命令由 App 按需执行 - 移植 App 全部对应 fixture 测试(dart_compat 55 例)+ FFI 双跑 12 例
- getStatus 改走 sbm_ffi parse_status_json(FRB 异步,Rust 线程池执行, 取代 Computer isolate),Dart 侧仅装配模型与更新滑窗状态 - 删除全部 Dart 解析实现(12 个模型 + windows_parser.dart),模型类保留 - 采集命令切换单一来源:ShellCmdType.cmd 读 FFI 清单,枚举值退化为 fallback,并以一致性测试锁定 - parse_status 支持 temp_divisor;Windows cpuBrand 从 Win32_Processor 提取 - 纯解析测试删除(行为由 crates/sbm_parser dart_compat 锁定), 模型/装配测试保留并接入 RustLib 测试初始化
- cargokit 插件壳与 sbm_ffi crate 同目录(cargokit 官方示例布局) - workspace member、FRB rust_root、pubspec path、各平台 glue 路径同步更新
- 手动触发,版本取自 monitor crate,tag monitor-v* 与 App 解耦 - Linux musl bin 复用进 Alpine 镜像,镜像内零编译 - reqwest 切 rustls,OpenSSL 移出依赖树(musl 静态编译前提) - 修复 Dockerfile 前端路径与 glibc 基镜像不匹配 - install.sh 重写适配新 release 结构,首装生成随机 JWT_SECRET
- 删除捏造功能(托盘/Touch Bar/多窗口/NUL 保活/SFTP 分页等) - 修正事实错误(Tab 列表、Windows C++、加密存储、类名/设置名) - 补全 monorepo 布局与 Rust 测试/FRB codegen 说明(en/zh/ja/de/fr/es) - README monitor 链接指向 monorepo;新增 dependabot 覆盖 monitor - rm: doc/adr(有意删除),清理其引用
|
Important Review skippedToo many files! This PR contains 279 files, which is 129 over the limit of 150. To get a review, narrow the scope: Upgrade to Pro+ to raise the limit. ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (8)
📒 Files selected for processing (287)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Deploying serverbox with
|
| Latest commit: |
1dd57ed
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://20d293e3.serverbox.pages.dev |
| Branch Preview URL: | https://frb.serverbox.pages.dev |
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Actionable comments posted: 6
🔎 Confirmed findings (6)
- medium Windows logical disks with no free space are silently dropped instead of being represented as a valid full disk.
parse_disksexplicitly filtersfree == 0, so a normal full C: volume (or a WMI record reporting zero FreeSpace) disappears fromServerStatus.disks; this also makes the Windows disk aggregate report no disk rather than 100% used. The Dart model accepts nullable fields but does not make zero a missing value, and the migrated parser's own required-field comment only calls for missing fields to be skipped. This is introduced by the new Rust Windows parser; the claim would be false if the established Dart parser intentionally excluded full volumes or the remote command guarantees FreeSpace is always positive. (inline) - high The velocity endpoints use a hard-coded server key and therefore return no velocity for the configured/default server. (inline)
- high Windows collection never populates ServerStatus.net, so monitor network totals and velocity remain zero on Windows. (inline)
- high Retention configuration and migration policy rows do not match cleanup behavior. Migration 003 creates policies for velocity_metrics, cpu_core_metrics, network_totals, enhanced_alerts, component_metrics, rule_executions, performance_metrics, and config_audit_log, while
DataCleanupService::cleanup_expired_datadeletes onlysystem_metricsand the legacyalertstable. The monitoring loop writesvelocity_metricsevery cycle (twice per update), so those rows grow without bound despite a configured 30-day policy; the same mismatch affects the other policy-covered tables. This is introduced by the new retention/migration implementation; it would be disproven if another cleanup worker consumedretention_policies, but no such consumer exists. (inline) - high The
serveCLI options do not affect the server at all.build_clidefines--addr,--cert, and--key(and environment bindings), buthandle_serveignores itsArgMatchesand constructs the server solely fromConfig::load;start_serverthen uses onlyget_server(). Runningserver_box_monitor serve --addr 127.0.0.1:9999or supplying--cert/--keytherefore still binds the config/default0.0.0.0:3770and remains plaintext. This is introduced by the new CLI/config integration; it would be disproven ifhandle_serveor a downstream call applied these matches, but no such application exists. (inline) - high The monitor exposes two incompatible CPU semantics from the same shared parser output:
sbm_parserproduces cumulative CPU ticks, and its Dart consumer computes usage from inter-sample deltas, butmonitor::adapt_cpucomputesSystemMetrics.cpu_usageas(total-idle)/totalover lifetime counters. Consequently the monitor's current/status, storedsystem_metrics.cpu_usage, and threshold rules use boot-time average CPU rather than current utilization, while its velocity endpoint uses delta-based per-core values and can show a materially different CPU percentage. (inline)
🧹 Additional findings from this change (not shown inline) (19)
- [medium] The WMI delta parser pairs the two samples by array index rather than by interface/disk name. If WMI returns the same instances in a different order between the two queries (which is allowed when providers enumerate interfaces/disks), the code subtracts counters from different devices and labels the result with the first sample's name, producing negative/huge rates or silently skipping valid devices. The same issue affects both Windows network speed and disk I/O. The claim would be false only if the WMI provider contract used by all supported hosts guarantees stable ordering across the one-second queries.
- [medium] Linux battery parsing loses the last power-supply record when the command output does not end in a blank line.
parse_batteriesonly callsparse_battery_blockwhen it encounters an empty line and never flushesblockafter the loop, so valid single-block or truncated/no-final-newline output yields an empty result (and multi-block output loses its final battery). This violates the stated malformed-input tolerance and differs from splitting records into blocks, where the final block is still parseable. The claim would be false if the command transport always guarantees a trailing blank line and that guarantee is part of the parser contract. - [high] Windows plugin builds cannot resolve the Rust crate because its CMake integration uses a path that climbs six directories before appending
crates/sbm_ffi. From the checked-incrates/sbm_ffi/windows/CMakeLists.txt, that resolves outside the repository rather than to the sibling crate (unlike Linux, which uses..). A Windows Flutter build will therefore fail during CMake/cargokit setup unless the build system happens to invoke this file with an unrelated working/source directory; this is false only if the generated Windows build relocates/rebasesCMAKE_CURRENT_SOURCE_DIRso that the six-level path actually points at the repository crate. - [medium] Native FFI initialization failure is unrecoverable at application startup:
_initAppawaitsRustLib.init()without a catch, andmainonly runsrunAppafter_initAppcompletes. If the native library is absent, has an ABI/load error, or the generated initializer fails on a supported target, the app exits before UI/data initialization; the only fallback implemented is command lookup fallback and there is no Dart status parser left to preserve status functionality. This is not a bug only if startup failure is explicitly the intended policy rather than the scope's required graceful initialization-failure behavior. - [medium] CPU-core usage is persisted as idle percentage rather than used percentage.
- [medium] Retention cleanup leaves velocity and CPU-core time-series rows permanently unbounded.
- [medium] A velocity database failure partially advances in-memory state and causes the next successful cycle to calculate across an uncommitted sample.
- [medium] Each monitoring cycle writes two velocity rows and two history points, with the first containing pre-update CPU data.
- [high] The server can run with a publicly documented/default JWT signing key, so anyone who can reach a protected endpoint can forge an HS256 Bearer token.
Config::default,normalize, andget_jwt_secretall fall back toyour-secret-key-change-this, while the checked-in example config and frontend also make the default deployment credentials predictable (admin/admin123). This violates the deployment security boundary unless operators always override both values, but the application does not require or validate that they were overridden. - [high] TLS configuration is not applied to the HTTP server. Even when
SBM_TLS_CERTandSBM_TLS_KEYpopulateServerConfig.tls,start_serveronly callsHttpServer::new(...).bind(&bind_addr)and never configures TLS. A deployment that follows the documented TLS configuration therefore serves login credentials, JWTs, and monitoring data over plaintext HTTP. - [medium] The velocity endpoint can return a mixed-time snapshot because it takes and releases the manager read lock for each component independently: velocity, network totals, and readiness are fetched by three separate
.read().awaitchains. The monitoring loop can acquire the manager write lock between those calls and update the processor (including two database writes), sonetwork,is_ready, and the speeds in one response may describe different samples. The same write lock is also held across awaited database writes inupdate_server_metrics, so a slow SQLite operation blocks all velocity API readers. - [medium] Malformed or unsupported
limitquery parameters are silently converted into an absent limit and the endpoint returns HTTP 200 with the full in-memory history.from_queryerrors are discarded, andserde_json::Value::as_u64()only accepts a JSON number; normal URL query values such as?limit=10are represented as strings, so the documented-looking request does not limit results. This makes query parsing non-observable to clients and permits an unbounded-by-request (up to the retained 1000 entries) response instead of a stable 400 for invalid input. - [medium] Internal database/error details are exposed directly to API clients. The velocity handlers build JSON errors with
format!("...: {}", e), andMonitorError::WebResponseError::error_responseserializesself.to_string()for propagated login/database failures. A SQLite failure (or other internal error) can therefore reveal SQL/schema/path information in a 500 response, rather than returning a stable generic error while logging the cause server-side. - [critical] Fresh production databases are seeded with a universally known admin credential: migration 001 inserts username
adminwith a bcrypt hash whose accompanying comment explicitly says the password isadmin123. The login endpoint accepts this account immediately and there is no forced password change or first-run secret provisioning, so any exposed fresh installation can be logged into with the baked-in password. This is introduced by the new initial migration; it would be disproven only if another startup step disables/replaces this account before the server accepts logins, which the initialization and auth paths do not do. - [high] The deployment configuration files are not actually loaded by either packaged deployment.
Config::loadonly readsconfig.toml/config.jsonand never parses.env; nevertheless Docker copies.env.exampleto.env, and the install script copies.envwithout adding a systemdEnvironmentFile. ThusDATABASE_URL,JWT_SECRET, host/port, and TLS environment settings in the shipped.envare ignored, causing the service to use code defaults (including the weak JWT fallback) unless variables happen to be exported externally. This is introduced by the new packaging/config path; it would be disproven if a parent process loads dotenv, but neither the Docker CMD nor the generated unit does so. - [high] The release CI workflow points at paths from a different repository layout and cannot complete in this monorepo: the frontend steps run
cd frontendand cachefrontend/package-lock.json, but the added frontend ismonitor/frontend; release packaging copiesconfig.json.exampleand.env.examplefrom the repository root, while the added files aremonitor/config.example.tomlandmonitor/.env.example. Consequently PR CI fails before testing/building, and the release job cannot create the documented archive. This is introduced by the added monitor workflow; it would be disproven only if those root paths were supplied by an unshown checkout/generated step, which they are not. - [medium] The retained Flutter tests cannot initialize the FFI library on Windows because the shared test loader never selects the Windows artifact.
- [medium] The new monitor documentation still directs users to the obsolete standalone repository wiki, so monitor configuration/usage documentation is not valid for the new workspace checkout.
- [medium] The monitor's disk aggregation disagrees with the shared parser/Dart disk contract and can double-count layered devices.
sbm_parser::types::disk_usagestops descending once a disk node has nonzero size/used data and deduplicates by path:kname, butmonitor::aggregate_disksadds every/devnode with size and then recursively walks children without that stop condition (and deduplicates only by path). On lsblk output containing a populated parent plus populated partitions, monitor API/rules/history report inflated disk totals and usage despite the same parsed status being correct for the app.
📜 Review details
Coverage
- scopes: 6/6 complete
| let size = json_u64(&d["Size"])?; | ||
| let free = json_u64(&d["FreeSpace"])?; | ||
| let fs = d["FileSystem"].as_str().unwrap_or("").to_string(); | ||
| if device_id.is_empty() || size == 0 || free == 0 || fs.is_empty() { |
There was a problem hiding this comment.
🎯 Correctness | 🟡 Medium
Windows logical disks with no free space are silently dropped instead of being represented as a valid full disk. parse_disks explicitly filters free == 0, so a normal full C: volume (or a WMI record reporting zero FreeSpace) disappears from ServerStatus.disks; this also makes the Windows disk aggregate report no disk rather than 100% used. The Dart model accepts nullable fields but does not make zero a missing value, and the migrated parser's own required-field comment only calls for missing fields to be skipped. This is introduced by the new Rust Windows parser; the claim would be false if the established Dart parser intentionally excluded full volumes or the remote command guarantees FreeSpace is always positive.
🧩 Analysis
- Change relation: introduced
- Confirmation: independently-verified
- Reachable: ✅
⚠️ The deleted legacy Dart parser was not available for direct comparison, so intentional historical exclusion cannot be ruled out solely from that implementation; however Win32_LogicalDisk permits a valid filesystem with Size > 0 and FreeSpace = 0, and the new parser's explicit filter demonstrably drops it.
🤖 Prompt for AI agents
In crates/sbm_parser/src/windows.rs, address this finding:
Windows logical disks with no free space are silently dropped instead of being represented as a valid full disk. `parse_disks` explicitly filters `free == 0`, so a normal full C: volume (or a WMI record reporting zero FreeSpace) disappears from `ServerStatus.disks`; this also makes the Windows disk aggregate report no disk rather than 100% used. The Dart model accepts nullable fields but does not make zero a missing value, and the migrated parser's own required-field comment only calls for missing fields to be skipped. This is introduced by the new Rust Windows parser; the claim would be false if the established Dart parser intentionally excluded full volumes or the remote command guarantees FreeSpace is always positive.
To have the bot fix this, comment @winnowl fix.
| .velocity_manager | ||
| .read() | ||
| .await | ||
| .get_server_velocity("server", interval) |
There was a problem hiding this comment.
🎯 Correctness | 🟠 High
The velocity endpoints use a hard-coded server key and therefore return no velocity for the configured/default server.
🧩 Analysis
- Change relation: introduced
- Confirmation: independently-verified
- Reachable: ✅
🤖 Prompt for AI agents
In monitor/src/api/server.rs, address this finding:
The velocity endpoints use a hard-coded server key and therefore return no velocity for the configured/default server.
To have the bot fix this, comment @winnowl fix.
| }); | ||
| status.batteries = windows::parse_batteries(get(commands::BATTERY)); | ||
| // 网速/磁盘 IO 为 WMI 双采样差分,直接产出速率: | ||
| // windows::parse_net_speed / windows::parse_diskio |
There was a problem hiding this comment.
🎯 Correctness | 🟠 High
Windows collection never populates ServerStatus.net, so monitor network totals and velocity remain zero on Windows.
🧩 Analysis
- Change relation: introduced
- Confirmation: independently-verified
- Reachable: ✅
🤖 Prompt for AI agents
In crates/sbm_parser/src/lib.rs, address this finding:
Windows collection never populates ServerStatus.net, so monitor network totals and velocity remain zero on Windows.
To have the bot fix this, comment @winnowl fix.
| info!("Starting data cleanup task"); | ||
|
|
||
| let metrics_deleted = self.cleanup_old_metrics().await?; | ||
| let alerts_deleted = self.cleanup_old_alerts().await?; |
There was a problem hiding this comment.
🔍 Data Integrity | 🟠 High
Retention configuration and migration policy rows do not match cleanup behavior. Migration 003 creates policies for velocity_metrics, cpu_core_metrics, network_totals, enhanced_alerts, component_metrics, rule_executions, performance_metrics, and config_audit_log, while DataCleanupService::cleanup_expired_data deletes only system_metrics and the legacy alerts table. The monitoring loop writes velocity_metrics every cycle (twice per update), so those rows grow without bound despite a configured 30-day policy; the same mismatch affects the other policy-covered tables. This is introduced by the new retention/migration implementation; it would be disproven if another cleanup worker consumed retention_policies, but no such consumer exists.
🧩 Analysis
- Change relation: introduced
- Confirmation: independently-verified
- Reachable: ✅
⚠️ The retention_policies table is not read anywhere in the repository, though future code outside this revision could theoretically consume it.⚠️ Some policy-covered tables are currently schema-only; the defect is concretely demonstrated for velocity_metrics and cpu_core_metrics, both of which are written by the monitoring path.
🤖 Prompt for AI agents
In monitor/src/db/cleanup.rs, address this finding:
Retention configuration and migration policy rows do not match cleanup behavior. Migration 003 creates policies for velocity_metrics, cpu_core_metrics, network_totals, enhanced_alerts, component_metrics, rule_executions, performance_metrics, and config_audit_log, while `DataCleanupService::cleanup_expired_data` deletes only `system_metrics` and the legacy `alerts` table. The monitoring loop writes `velocity_metrics` every cycle (twice per update), so those rows grow without bound despite a configured 30-day policy; the same mismatch affects the other policy-covered tables. This is introduced by the new retention/migration implementation; it would be disproven if another cleanup worker consumed `retention_policies`, but no such consumer exists.
To have the bot fix this, comment @winnowl fix.
| Ok(()) | ||
| } | ||
|
|
||
| async fn handle_serve(matches: &clap::ArgMatches) -> anyhow::Result<()> { |
There was a problem hiding this comment.
🔍 Compatibility | 🟠 High
The serve CLI options do not affect the server at all. build_cli defines --addr, --cert, and --key (and environment bindings), but handle_serve ignores its ArgMatches and constructs the server solely from Config::load; start_server then uses only get_server(). Running server_box_monitor serve --addr 127.0.0.1:9999 or supplying --cert/--key therefore still binds the config/default 0.0.0.0:3770 and remains plaintext. This is introduced by the new CLI/config integration; it would be disproven if handle_serve or a downstream call applied these matches, but no such application exists.
🧩 Analysis
- Change relation: introduced
- Confirmation: independently-verified
- Reachable: ✅
⚠️ No runtime execution was performed; conclusion follows directly from the reachable call chain.
🤖 Prompt for AI agents
In monitor/src/cli/cli.rs, address this finding:
The `serve` CLI options do not affect the server at all. `build_cli` defines `--addr`, `--cert`, and `--key` (and environment bindings), but `handle_serve` ignores its `ArgMatches` and constructs the server solely from `Config::load`; `start_server` then uses only `get_server()`. Running `server_box_monitor serve --addr 127.0.0.1:9999` or supplying `--cert/--key` therefore still binds the config/default `0.0.0.0:3770` and remains plaintext. This is introduced by the new CLI/config integration; it would be disproven if `handle_serve` or a downstream call applied these matches, but no such application exists.
To have the bot fix this, comment @winnowl fix.
| /// CPU:汇总行(id == "cpu",BSD 无汇总则取首核)计算使用率; | ||
| /// 逐核转为 CpuCoreTime(used = total - idle)。 | ||
| /// 注意:单次采样的累计 ticks 反映开机以来均值;差分改造见 ADR Phase 1b | ||
| fn adapt_cpu(cores: &[CpuCore]) -> (f32, Vec<CpuCoreTime>) { |
There was a problem hiding this comment.
🎯 Correctness | 🟠 High
The monitor exposes two incompatible CPU semantics from the same shared parser output: sbm_parser produces cumulative CPU ticks, and its Dart consumer computes usage from inter-sample deltas, but monitor::adapt_cpu computes SystemMetrics.cpu_usage as (total-idle)/total over lifetime counters. Consequently the monitor's current /status, stored system_metrics.cpu_usage, and threshold rules use boot-time average CPU rather than current utilization, while its velocity endpoint uses delta-based per-core values and can show a materially different CPU percentage.
🧩 Analysis
- Change relation: introduced
- Confirmation: independently-verified
- Reachable: ✅
⚠️ The exact user-visible impact depends on the host's uptime and workload history; the concrete Linux path is nevertheless sufficient to reproduce the semantic mismatch.
🤖 Prompt for AI agents
In monitor/src/monitoring/monitoring.rs, address this finding:
The monitor exposes two incompatible CPU semantics from the same shared parser output: `sbm_parser` produces cumulative CPU ticks, and its Dart consumer computes usage from inter-sample deltas, but `monitor::adapt_cpu` computes `SystemMetrics.cpu_usage` as `(total-idle)/total` over lifetime counters. Consequently the monitor's current `/status`, stored `system_metrics.cpu_usage`, and threshold rules use boot-time average CPU rather than current utilization, while its velocity endpoint uses delta-based per-core values and can show a materially different CPU percentage.
To have the bot fix this, comment @winnowl fix.
CI failure root-cause analysisThe three failing suites share one root cause: their Attribution No change set or build diagnostics were provided, so the introducing change cannot be attributed. The likely cause is a missing or skipped native-library build, an incorrect artifact path, or a CI step that no longer publishes/copies Verifiable fix Before running the Dart tests, build the Rust/native target that produces Same root cause: 88112927769:test/server_status_update_req_test.dart, 88112927769:test/frb_parser_test.dart, 88112927769:test/windows_test.dart Incremental value: root cause, attributed to this change, grouped same-root-cause failures, verifiable fix; confidence 98%. Passing CI ≠ absence of defects (§29.4). |
CI failure root-cause analysisThree test suites fail during Attribution No change set or build-step diagnostics were provided, so the responsible change cannot be attributed. The likely regression is a missing, skipped, or relocated native-library build step, or a working-directory/path change in CI. Verifiable fix Before running the Dart tests, build the native target that produces Same root cause: 88157066047: Incremental value: root cause, attributed to this change, grouped same-root-cause failures, verifiable fix; confidence 98%. Passing CI ≠ absence of defects (§29.4). |
There was a problem hiding this comment.
Actionable comments posted: 0
✅ No blocking issues found — approving.
🧹 Additional findings from this change (not shown inline) (2)
- [medium] Persisted per-core CPU usage is inverted relative to the monitor's CPU semantics.
- [high] Deployments that omit JWT_SECRET use a publicly documented weak fallback, allowing anyone to forge a valid bearer token for any username and access all protected metrics/velocity APIs.
Config::defaultand Go-format normalization both selectyour-secret-key-change-thiswhen the environment is absent, and the Docker compose file explicitly suppliesyour-secret-key-change-this-in-productionas its fallback.
🗑️ Suppressed and duplicate diagnostics (1)
- CPU-core usage is persisted as idle percentage rather than used percentage.
📜 Review details
Coverage
- scopes: 0/7 complete
CI failure root-cause analysisThe three failing suites share one root cause: their Attribution Not determinable from the supplied diagnostics. The likely regression point is the CI setup or build configuration that should produce or expose Verifiable fix Before running the Dart tests, build the native Rust artifact that produces Same root cause: /home/runner/work/flutter_server_box/flutter_server_box/test/server_status_update_req_test.dart, /home/runner/work/flutter_server_box/flutter_server_box/test/frb_parser_test.dart, /home/runner/work/flutter_server_box/flutter_server_box/test/windows_test.dart Incremental value: root cause, attributed to this change, grouped same-root-cause failures, verifiable fix; confidence 98%. Passing CI ≠ absence of defects (§29.4). |
Summary
This PR finishes the monorepo migration with three main blocks, plus follow-up hardening:
1. migrate: merge
rust/andrust_builder/intocrates/sbm_ffirust_root, pubspec paths, and per-platform glue updated in synccargo test --workspacegreen, 12/12 FFI parity tests pass,flutter build macos --debugbuilds through cargokit for real2. feat: monitor release pipeline
monitor-v*tags), decoupled from the app'sv1.0.xinstall.shrewritten for the new release layout; fresh installs generate a randomJWT_SECRET3. docs: full audit of the docs site (en/zh/ja/de/fr/es)
4. Follow-ups on this branch
Notes
server-box-monitorghcr image to public in package settings if desiredSummary
No changes summarized.