Skip to content

Commit b008b15

Browse files
Improve cross-border data gate and consent improvements
Refactor SECTL data transmission to require dual consent: - Introduced `IsServicesPolicyAccepted` and updated `IsEgressAllowed` to enforce both SECTL policy and cross-border notice acceptance. - Enhanced `EnsureTransferAcceptedAsync` to sequentially collect user consent for SECTL services and cross-border data transfer. Updated UI logic to reflect new consent requirements: - Added `IsSectlEgressConsented` in `FirstRunOobeViewModel` to bind UI state to dual consent status. - Adjusted `FirstRunOobeWindow.axaml` and `VerificationSettingsPage` to enable controls only when both consents are granted. Improved default behavior for attestation uploads: - Treated `AttestationUploadMode.Unset` as "do not upload" until explicit user choice is made. Clarified SECTL traffic gating: - Ensured `OnlineStatusMode` does not control SECTL access. - Excluded NIST Beacon from SECTL gating. Added mandatory reading periods and checkboxes to consent dialogs.
1 parent 6c1cb2c commit b008b15

6 files changed

Lines changed: 38 additions & 8 deletions

File tree

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ Keep this map short and stable. When code moves, AI agents should re-read the mo
153153
- Point-call students and lottery prizes use `RecordId` as the internal stable identity for history/fairness. The visible `Id` field is optional display metadata only and must not be required by import, draw, or history logic.
154154
- A roll-call student or lottery prize candidate must be enabled and have a nonblank `Id` or `Name`. Import may map either column and excludes rows where both are blank.
155155
- Picking `ClearRecord` controls temporary draw records only; do not clear persistent profile histories from that setting. RollCall and QuickDraw share the same student temporary record store, while Lottery uses prize temporary records.
156-
- Privacy settings split Sentry upload from online status reporting: `SentryTelemetryEnabled` only controls `SecRandom/Services/Telemetry/`, while `OnlineStatusMode` only controls `SecRandom/Services/OnlineStatusService.cs`. In this fork both channels default to off (`SentryTelemetryEnabled = false`, `OnlineStatusMode = Off`, which also disables `PlatformUsageReportService`), because their data still goes to the upstream SecRandom/SECTL infrastructure — the Sentry DSN and the SECTL endpoints are unchanged. Any surface that exposes these toggles must state that enabling them sends data to SecRandom and SECTL rather than to this fork; the OOBE privacy step and the privacy settings page both carry that notice, and the OOBE privacy policy repeats it per section. The OOBE legal page also carries a separate SECTL disclosure (`C_SecRandomServicesClause`: online features require a separate agreement with SECTL, an independent entity, which gains access to uploaded data; `C_SecRandomServicesDisclaimer`: using the online features accepts SecRandom's terms and privacy policy and forms a direct service relationship with SECTL to which VeriRandom is not a party) with the copied `SecRandomPrivacyPolicy` text (the fork's policy with the product name changed back to SecRandom) and its own checkbox. That acknowledgement is **optional** during first-run setup (the page says so in bold) and does NOT gate `IsPrivacyPolicyOnlyRequired()`: `SecRandomServicesConsent` demands it, through the same second-level confirmation dialog, the first time an action would actually send data to SecRandom/SECTL — switching to formal notarization or enabling Sentry telemetry / online status — and declining reverts that action. The formal-notarization confirmation dialog carries the upstream description plus a bold "note" and warning, and appends the SECTL disclosure with a required checkbox whenever the consent version is below current. Every deliberate-consent dialog in this flow (the notarization mode switch and the online-services acknowledgement) must arm `ConfirmDialogGate`, so its confirm button stays disabled for a five-second minimum reading period in addition to the required checkboxes.
156+
- Privacy settings split Sentry upload from online status reporting: `SentryTelemetryEnabled` only controls `SecRandom/Services/Telemetry/`, while `OnlineStatusMode` only controls `SecRandom/Services/OnlineStatusService.cs`. In this fork both channels default to off (`SentryTelemetryEnabled = false`, `OnlineStatusMode = Off`, which also disables `PlatformUsageReportService`), because their data still goes to the upstream SecRandom/SECTL infrastructure — the Sentry DSN and the SECTL endpoints are unchanged. Any surface that exposes these toggles must state that enabling them sends data to SecRandom and SECTL rather than to this fork; the OOBE privacy step and the privacy settings page both carry that notice, and the OOBE privacy policy repeats it per section. The OOBE legal page also carries a separate SECTL disclosure (`C_SecRandomServicesClause`: online features require a separate agreement with SECTL, an independent entity, which gains access to uploaded data; `C_SecRandomServicesDisclaimer`: using the online features accepts SecRandom's terms and privacy policy and forms a direct service relationship with SECTL to which VeriRandom is not a party) with the copied `SecRandomPrivacyPolicy` text (the fork's policy with the product name changed back to SecRandom) and its own checkbox. That acknowledgement is **optional** during first-run setup (the page says so in bold) and does NOT gate `IsPrivacyPolicyOnlyRequired()`: `SecRandomServicesConsent` demands it, through the same second-level confirmation dialog, the first time an action would actually send data to SecRandom/SECTL — switching to formal notarization or enabling Sentry telemetry / online status — and declining reverts that action. `SectlTrafficPolicy` (app-layer) is the one gate every path that could send data to SECTL must pass, and it requires **both** acknowledgements: upstream's SECTL online-services policy (`AcceptedSecRandomServicesVersion`) and the fork's cross-border data transfer notice (`AcceptedCrossBorderTransferVersion`). Declining either one closes sign-in, heartbeat, cloud backup, online status, usage counters, the version report, and proof attestation. Never gate SECTL traffic on `OnlineStatusMode` alone — `PlatformVersionReportService` is deliberately not privacy-gated, so it must consult this policy explicitly. Ordinary-draw attestation additionally requires `VerificationSettingsConfig.AttestationUpload == Enabled`, and `Unset` (never asked) must never upload. The formal-notarization confirmation dialog carries the upstream description plus a bold "note" and warning, and appends the SECTL disclosure with a required checkbox whenever the consent version is below current. Every deliberate-consent dialog in this flow (the notarization mode switch and the online-services acknowledgement) must arm `ConfirmDialogGate`, so its confirm button stays disabled for a five-second minimum reading period in addition to the required checkboxes.
157157
- `OnlineStatusMode` gates the SECTL online-status and usage-counter channels (`SecRandom/Services/OnlineStatusService.cs`, `PlatformUsageReportService`) but deliberately not `PlatformVersionReportService`; a version figure is not a privacy-gated channel, so a fork-level "no SECTL traffic" consent must gate it explicitly instead of relying on `OnlineStatusMode`.
158158
- Usage statistics are a third, separate channel: `PlatformUsageReportService` counts roll-call, lottery, and app-launch events through the platform-neutral `UsageMetricCounter` and posts `platform_id`/`field_key`/`delta` daily increments to `https://appwrite.sectl.cn/api/stats/usage/increment`. The payload deliberately carries **no** time field: the service side stamps each increment with its own receive time and owns the weekly/monthly/lifetime figures, so the client's clock and timezone can never move an event into another bucket and no client-generated period string (`period`) is sent at all. The client therefore reports only the `daily_*` field keys (`daily_roll_call_count`, `daily_lottery_count`, `daily_app_launch_count`) and keeps just the current local day's counts plus the unsent increment queue in `data/config/usage-stats.json`. It is gated by `PrivacySettingsConfig.OnlineStatusMode == Off`, counts a draw only after the transactional commit succeeded, persists counters by atomic replace, restores a failed increment for the next attempt (so a partial batch is never replayed wholesale), and must stay best-effort — no statistics failure may reach a draw. The service-side counters are shared with the v2 client, which still reports its own period strings, so a server that starts deriving the coarser periods from daily rows must not do so for v2 traffic as well.
159159
- Version usage is the fourth statistics figure: `PlatformVersionReportService` posts `platform_id`/`version`/`device_uuid` to `https://appwrite.sectl.cn/api/stats/version`, where the service dedups by identity and keeps one current version per identity (the sum over versions is the platform's deduplicated user count, which `/api/fields/values` and `/api/stats/usage/increment` cannot produce). The account identity the API also accepts (`user_id`) is deliberately **never** sent: a version figure must not be tied to a SECTL account, so the lowercase `DeviceUuidStore` device UUID is the only identity and the payload carries nothing else. It is deliberately **not** gated by `OnlineStatusMode` either — the version mix is the project's own release baseline — and its payload stays minimal for that reason: the version plus that one device UUID, never an address, region, or account. It reports exactly once per process (the API asks for one report per start; no polling, no retry storm, no state), so nothing has to be re-reported when a user signs in or out. The reported version is `GlobalConstants.Version` **including its `v` prefix**, because the service groups people by that exact string (do not switch it to `Tag` or strip the prefix). The report must stay on the thread pool (`Task.Run` from `ExecuteAsync`): `Host.StartAsync` runs a hosted service synchronously up to its first incomplete await, so the device-UUID file access and the HTTP send must neither run on nor delay the startup thread; a rejected or unreachable endpoint only costs statistics.

‎DownStream_Change.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,9 +326,11 @@
326326
**意图**:SECTL 的服务器位于中华人民共和国境外(当前节点:德国巴伐利亚邦阿多特多夫,IP 159.195.70.108),因此把所有可能向 SECTL 发送数据的路径收敛到**一个**「跨境数据传输须知」闸门下;同时把原本不可关闭、默认开启的「普通模式抽取后自动向 SECTL 申请签名」改为**没有任何默认值**的主动二选一。
327327

328328
**判定口径(必须保持)**:
329+
- `IsEgressAllowed` 要求**同时**满足两项确认:上游的 SECTL 线上服务政策(`AcceptedSecRandomServicesVersion`)与本分支的跨境数据传输须知(`AcceptedCrossBorderTransferVersion`)。任一项未接受 → 登录、心跳、云备份、在线状态、使用统计、版本使用量、抽取上传全部停用。
329330
- `AttestationUploadMode.Unset` 表示「还没问过用户」,**必须当作不上传**处理。
330331
- 绝不能用 `OnlineStatusMode` 代表「是否允许访问 SECTL」:上游的 `PlatformVersionReportService` **刻意不受** `OnlineStatusMode` 约束,必须显式走本闸门。
331332
- 本闸门不得拦截 NIST Beacon(`NistBeaconClient` 访问的是 NIST,不是 SECTL)。
333+
- 补签入口 `SectlTrafficPolicy.EnsureTransferAcceptedAsync` **先**收上游线上服务政策确认,**再**收跨境须知;任一被拒绝则闸门保持关闭。
332334

333335
**新增文件**:
334336

‎SecRandom/Services/Consent/SectlTrafficPolicy.cs‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,20 @@ public static bool IsTransferNoticeAccepted(MainConfigHandler configHandler) =>
2727
>= FirstRunOobeService.CurrentCrossBorderTransferVersion;
2828

2929
/// <summary>
30-
/// The single answer to "may anything leave this device for SECTL?".
30+
/// Upstream's SECTL online-services privacy policy acknowledgement.
31+
/// </summary>
32+
public static bool IsServicesPolicyAccepted(MainConfigHandler configHandler) =>
33+
configHandler.Data.General.Basic.AcceptedSecRandomServicesVersion
34+
>= FirstRunOobeService.CurrentSecRandomServicesVersion;
35+
36+
/// <summary>
37+
/// The single answer to "may anything leave this device for SECTL?". It requires **both**
38+
/// acknowledgements — upstream's online-services policy and the fork's cross-border notice — so a
39+
/// user who declined either one keeps every SECTL path closed: sign-in, heartbeat, cloud backup,
40+
/// online status, usage counters, the version report, and proof attestation.
3141
/// </summary>
3242
public static bool IsEgressAllowed(MainConfigHandler configHandler) =>
33-
IsTransferNoticeAccepted(configHandler);
43+
IsTransferNoticeAccepted(configHandler) && IsServicesPolicyAccepted(configHandler);
3444

3545
/// <summary>
3646
/// Ordinary-draw replay attestation is an additional, explicit choice on top of the egress consent.
@@ -54,6 +64,12 @@ public static void MarkTransferNoticeAccepted(MainConfigHandler configHandler)
5464
/// </summary>
5565
public static async Task<bool> EnsureTransferAcceptedAsync(MainConfigHandler configHandler, TopLevel owner)
5666
{
67+
// Both acknowledgements gate SECTL traffic, so the late sign-off collects them in order:
68+
// upstream's online-services policy first, then the fork's cross-border notice. Each shows its own
69+
// dialog, and declining either one leaves the gate closed.
70+
if (!await SecRandomServicesConsent.EnsureAsync(configHandler, owner))
71+
return false;
72+
5773
if (IsTransferNoticeAccepted(configHandler))
5874
return true;
5975

‎SecRandom/ViewModels/FirstRunOobeViewModel.cs‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,21 @@ partial void OnSelectedStepChanged(int value)
154154
partial void OnAcceptedPrivacyPolicyChanged(bool value) => OnPropertyChanged(nameof(CanContinue));
155155
partial void OnAcceptedGplChanged(bool value) => OnPropertyChanged(nameof(CanContinue));
156156
partial void OnAcceptedVerificationNoticeChanged(bool value) => OnPropertyChanged(nameof(CanContinue));
157-
partial void OnAcceptedSecRandomServicesChanged(bool value) => OnPropertyChanged(nameof(CanContinue));
158-
partial void OnAcceptedCrossBorderTransferChanged(bool value) => OnPropertyChanged(nameof(CanContinue));
157+
partial void OnAcceptedSecRandomServicesChanged(bool value) => NotifyEgressConsentChanged();
158+
partial void OnAcceptedCrossBorderTransferChanged(bool value) => NotifyEgressConsentChanged();
159+
160+
/// <summary>
161+
/// Both acknowledgements gate SECTL traffic, so the "submit" option is selectable only once the user
162+
/// has accepted upstream's online-services policy and the fork's cross-border notice. The "do not
163+
/// submit" option stays selectable regardless, so the step can always be completed.
164+
/// </summary>
165+
public bool IsSectlEgressConsented => AcceptedSecRandomServices && AcceptedCrossBorderTransfer;
166+
167+
private void NotifyEgressConsentChanged()
168+
{
169+
OnPropertyChanged(nameof(IsSectlEgressConsented));
170+
OnPropertyChanged(nameof(CanContinue));
171+
}
159172

160173
partial void OnAttestationUploadChanged(AttestationUploadMode value)
161174
{

‎SecRandom/Views/FirstRunOobeWindow.axaml‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,10 @@
178178
<TextBlock Text="{x:Static langs:Resources.C_AttestationBody}" TextWrapping="Wrap"
179179
Foreground="{DynamicResource TextFillColorSecondaryBrush}" />
180180
<RadioButton GroupName="OobeAttestationUpload"
181-
IsEnabled="{Binding ViewModel.AcceptedCrossBorderTransfer}"
181+
IsEnabled="{Binding ViewModel.IsSectlEgressConsented}"
182182
IsChecked="{Binding ViewModel.AttestationUploadEnabledChoice, Mode=TwoWay}"
183183
Content="{x:Static langs:Resources.O_AttestationOn}" />
184184
<RadioButton GroupName="OobeAttestationUpload"
185-
IsEnabled="{Binding ViewModel.AcceptedCrossBorderTransfer}"
186185
IsChecked="{Binding ViewModel.AttestationUploadDisabledChoice, Mode=TwoWay}"
187186
Content="{x:Static langs:Resources.O_AttestationOff}" />
188187
</StackPanel>

‎SecRandom/Views/SettingsPages/General/VerificationSettingsPage.axaml.cs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ private void RefreshAttestationUpload()
5151
var mode = ConfigHandler.Data.General.Verification.AttestationUpload;
5252
AttestationUploadOnRadio.IsChecked = mode == AttestationUploadMode.Enabled;
5353
AttestationUploadOffRadio.IsChecked = mode == AttestationUploadMode.Disabled;
54-
AttestationUploadGroup.IsEnabled = SectlTrafficPolicy.IsTransferNoticeAccepted(ConfigHandler);
54+
AttestationUploadGroup.IsEnabled = SectlTrafficPolicy.IsEgressAllowed(ConfigHandler);
5555
}
5656
finally
5757
{

0 commit comments

Comments
 (0)